Flutter’da Future.wait Kullanımı: Asenkron İşlemleri Etkili Yönetme

Flutter uygulamaları geliştirirken, asenkron işlemleri yönetmek günlük rutinimizin önemli bir parçasıdır. Ağ istekleri, veritabanı sorguları, dosya işlemleri gibi zaman alan görevleri etkili bir şekilde yönetmek, kullanıcı deneyimini doğrudan etkiler. Bu makalede, Flutter’da birden fazla asenkron işlemi paralel olarak yönetmenin güçlü bir yolu olan Future.wait fonksiyonunu derinlemesine inceleyeceğiz.

İçindekiler

  1. Future.wait Nedir?
  2. Temel Kullanım
  3. Hata Yönetimi
  4. Performans Optimizasyonu
  5. Gerçek Dünya Senaryoları
  6. İleri Düzey Kullanım
  7. Flutter UI ile Entegrasyon
  8. En İyi Uygulamalar ve İpuçları
  9. Sonuç

Future.wait Nedir?

Future.wait, Dart dilinde (ve dolayısıyla Flutter’da) birden fazla asenkron işlemin tamamlanmasını beklemek ve sonuçlarını toplamak için kullanılan güçlü bir yöntemdir. Bu fonksiyon, bir Future listesi alır ve listedeki tüm Future’lar tamamlandığında sonuçların bir listesiyle tamamlanan yeni bir Future döndürür.

Resmi Flutter dokümantasyonunda belirtildiği gibi:

“Future.wait, birden fazla Future’ın tamamlanmasını bekler ve sonuçlarını toplar. Verilen tüm Future’lar tamamlandığında, sonuçlarıyla veya herhangi biri başarısız olursa bir hatayla tamamlanan bir Future döndürür.”

Future.wait fonksiyonunun temel amacı, birden fazla asenkron işlemi eşzamanlı olarak çalıştırmak ve tüm sonuçları bir kerede elde etmektir. Bu, özellikle birbirine bağımlı olmayan işlemleri paralel olarak çalıştırarak uygulamanızın performansını önemli ölçüde artırabilir.

Temel Kullanım

Future.wait kullanımının en temel hali, bir Future listesi alıp tüm Future’ların tamamlanmasını beklemektir. İşte basit bir örnek:

import 'dart:async';

void main() async {
  // Basit Future'lar tanımlama
  Future<String> future1 = Future.delayed(Duration(seconds: 3), () => 'Future 1 tamamlandı');
  Future<String> future2 = Future.delayed(Duration(seconds: 5), () => 'Future 2 tamamlandı');
  Future<String> future3 = Future.delayed(Duration(seconds: 7), () => 'Future 3 tamamlandı');

  print('Tüm Future\'lar başlatıldı, sonuçlar bekleniyor...');

  // Future.wait ile tüm Future'ların tamamlanmasını bekleme
  List<String> results = await Future.wait([future1, future2, future3]);

  // Sonuçları yazdırma
  print('Tüm Future\'lar tamamlandı!');
  print('Sonuçlar: $results');

  // Sonuçlara tek tek erişim
  print('Future 1 sonucu: ${results[0]}');
  print('Future 2 sonucu: ${results[1]}');
  print('Future 3 sonucu: ${results[2]}');
}

Bu örnekte, üç farklı Future tanımlıyoruz, her biri farklı sürelerde tamamlanıyor. Future.wait kullanarak tüm Future’ların tamamlanmasını bekliyoruz ve sonuçları bir liste olarak alıyoruz. Önemli bir nokta, sonuçların listedeki sırasının, Future’ların listeye eklenme sırasıyla aynı olmasıdır.

Future.wait farklı tiplerde Future’larla da çalışabilir:

import 'dart:async';

void main() async {
  // Farklı tiplerde Future'lar tanımlama
  Future<int> getNumber() async {
    await Future.delayed(Duration(seconds: 3));
    return 42;
  }

  Future<String> getText() async {
    await Future.delayed(Duration(seconds: 5));
    return "Merhaba Dünya";
  }

  Future<List<String>> getList() async {
    await Future.delayed(Duration(seconds: 7));
    return ["Foo", "Bar", "Baz"];
  }

  Future<Map<String, dynamic>> getMap() async {
    await Future.delayed(Duration(seconds: 2));
    return {"isim": "John", "yaş": 30, "meslek": "Doctor"};
  }

  print('Tüm Future\'lar başlatıldı, sonuçlar bekleniyor...');

  // Future.wait ile farklı tiplerdeki Future'ların tamamlanmasını bekleme
  var results = await Future.wait([
    getNumber(),
    getText(),
    getList(),
    getMap()
  ]);

  // Sonuçları yazdırma
  print('Tüm Future\'lar tamamlandı!');
  print('Sayı: ${results[0]}');
  print('Metin: ${results[1]}');
  print('Liste: ${results[2]}');
  print('Harita: ${results[3]}');
}

Bu örnekte, farklı veri tipleri döndüren Future’ları bir arada kullanıyoruz. Future.wait bu durumda da sorunsuz çalışır ve her Future’ın sonucunu doğru tipte döndürür.

Hata Yönetimi

Future.wait kullanırken hata yönetimi önemli bir konudur. Varsayılan olarak, listedeki Future’lardan herhangi biri başarısız olursa, Future.wait tarafından döndürülen Future da başarısız olur ve ilk hatayı fırlatır. İşte temel bir hata yakalama örneği:

import 'dart:async';

void main() async {
  // Başarılı ve başarısız Future'lar tanımlama
  Future<String> successFuture() async {
    await Future.delayed(Duration(seconds: 3));
    return "Başarılı işlem";
  }

  Future<String> failingFuture() async {
    await Future.delayed(Duration(seconds: 5));
    throw Exception("Bir hata oluştu!");
  }

  print('Future\'lar başlatıldı, sonuçlar bekleniyor...');

  // try-catch bloğu ile hata yakalama
  try {
    List<String> results = await Future.wait([
      successFuture(),
      failingFuture()
    ]);

    print('Bu satır çalışmayacak çünkü bir hata oluşacak');
    print('Sonuçlar: $results');
  } catch (e) {
    print('Hata yakalandı: $e');
  }

  print('İşlem tamamlandı.');
}

Future.wait fonksiyonu, hata yönetimi için iki önemli parametre sunar: eagerError ve cleanUp.

eagerError Parametresi

eagerError parametresi, bir hata oluştuğunda Future.wait‘in davranışını kontrol eder:

  • eagerError: true (varsayılan): İlk hata oluştuğunda hemen hata fırlatır.
  • eagerError: false: Tüm Future’ların tamamlanmasını bekler, ancak yine de ilk hatayı fırlatır.
import 'dart:async';

void main() async {
  // Birden fazla hata fırlatan Future'lar
  Future<String> failingFuture1() async {
    await Future.delayed(Duration(seconds: 3));
    throw Exception("Hata 1");
  }

  Future<String> failingFuture2() async {
    await Future.delayed(Duration(seconds: 5));
    print("Some progress");
    throw Exception("Hata 2");
  }

  // eagerError = true ile ilk hatada hemen dönüş
  try {
    await Future.wait(
      [failingFuture1(), failingFuture2()],
      eagerError: true
    );
  } catch (e) {
    print('eagerError = true ile yakalanan ilk hata: $e');
  }
  
  // output 
  // -----------------
  // eagerError = true ile yakalanan ilk hata: Exception: Hata 1
  // Some progress (failingFuture2)
  // -----------------
  
  //========================================
  
  // eagerError = false ile tüm Future'ların tamamlanmasını bekleme
  try {
    await Future.wait(
      [failingFuture1(), failingFuture2()],
      eagerError: false
    );
  } catch (e) {
    print('eagerError = false ile yakalanan hata: $e');
  }
  
   // output  
   //-----
   // Some progress (failingFuture2)
   // eagerError = false ile yakalanan hata: Exception: Hata 1
   //------

  
  
}

cleanUp Parametresi

cleanUp parametresi, bir hata durumunda başarılı Future’ların sonuçlarını temizlemek için kullanılabilir. Bu, özellikle kaynakları serbest bırakmak veya açık bağlantıları kapatmak gibi durumlarda faydalıdır:

import 'dart:async';

void main() async {
  // Kaynak açan ve hata fırlatan Future'lar
  Future<Resource> createResource1() async {
    await Future.delayed(Duration(seconds: 3));
    print('Kaynak 1 oluşturuldu');
    return Resource('Kaynak 1');
  }

  Future<Resource> createResource2() async {
    await Future.delayed(Duration(seconds: 5));
    print('Kaynak 2 oluşturuldu');
    return Resource('Kaynak 2');
  }

  Future<Resource> failingResource() async {
    await Future.delayed(Duration(seconds: 7));
    throw Exception("Kaynak oluşturma hatası!");
  }

  // cleanUp fonksiyonu ile kaynakları temizleme
  try {
    await Future.wait(
      [createResource1(), createResource2(), failingResource()],
      cleanUp: (Resource resource) {
        print('${resource.name} temizleniyor...');
        resource.dispose();
      }
    );
  } catch (e) {
    print('Hata yakalandı: $e');
  }
}

class Resource {
  final String name;

  Resource(this.name);

  void dispose() {
    print('$name kapatıldı');
  }
}

Bu örnekte, cleanUp fonksiyonu hata durumunda başarılı olan Future’ların sonuçlarını (bu durumda Resource nesnelerini) temizlemek için kullanılır. Bu, bellek sızıntılarını önlemek ve kaynakları düzgün bir şekilde serbest bırakmak için önemlidir.

Performans Optimizasyonu

Future.wait‘in en önemli avantajlarından biri, birden fazla asenkron işlemi paralel olarak çalıştırarak performansı artırmasıdır. Aşağıdaki örnek, sıralı çalıştırma ile paralel çalıştırma arasındaki performans farkını göstermektedir:

import 'dart:async';

Future<void> main() async {
  // Test için kullanılacak fonksiyonlar
  Future<String> task1() async {
    await Future.delayed(Duration(seconds: 7));
    return 'Görev 1 tamamlandı';
  }

  Future<String> task2() async {
    await Future.delayed(Duration(seconds: 5));
    return 'Görev 2 tamamlandı';
  }

  Future<String> task3() async {
    await Future.delayed(Duration(seconds: 2));
    return 'Görev 3 tamamlandı';
  }

  // Sıralı çalıştırma
  print('Sıralı çalıştırma başlatılıyor...');
  final sequentialStart = DateTime.now();

  final result1 = await task1();
  print(result1);

  final result2 = await task2();
  print(result2);

  final result3 = await task3();
  print(result3);

  final sequentialEnd = DateTime.now();
  final sequentialDuration = sequentialEnd.difference(sequentialStart);
  print('Sıralı çalıştırma süresi: ${sequentialDuration.inMilliseconds} ms');

  // Paralel çalıştırma (Future.wait)
  print('\nParalel çalıştırma başlatılıyor...');
  final parallelStart = DateTime.now();

  final results = await Future.wait([
    task1(),
    task2(),
    task3()
  ]);

  results.forEach(print);

  final parallelEnd = DateTime.now();
  final parallelDuration = parallelEnd.difference(parallelStart);
  print('Paralel çalıştırma süresi: ${parallelDuration.inMilliseconds} ms');

  // Performans karşılaştırması
  final improvement = sequentialDuration.inMilliseconds / parallelDuration.inMilliseconds;
  print('\nParalel çalıştırma, sıralı çalıştırmadan ${improvement.toStringAsFixed(2)} kat daha hızlı');
}

Bu örnekte, üç asenkron görev hem sıralı hem de paralel olarak çalıştırılır ve süreleri karşılaştırılır. Sıralı çalıştırmada, her görev bir önceki tamamlandıktan sonra başlar, bu nedenle toplam süre tüm görevlerin sürelerinin toplamıdır (bu durumda yaklaşık 6 saniye). Paralel çalıştırmada ise, tüm görevler aynı anda başlar ve toplam süre en uzun süren görevin süresidir (bu durumda yaklaşık 3 saniye).

Büyük veri setleriyle çalışırken, veriyi parçalara ayırıp her parçayı paralel olarak işlemek için Future.wait kullanmak da etkili bir stratejidir:

import 'dart:async';
import 'dart:math';

Future<void> main() async {
  final random = Random();

  // Büyük veri seti oluşturma
  final dataSize = 10000;
  final List<int> data = List.generate(dataSize, (_) => random.nextInt(1000));

  print('Veri seti oluşturuldu, boyut: $dataSize');

  // Veri setini parçalara ayırma
  final chunkSize = 2000;
  final chunks = <List<int>>[];

  for (var i = 0; i < dataSize; i += chunkSize) {
    final end = (i + chunkSize < dataSize) ? i + chunkSize : dataSize;
    chunks.add(data.sublist(i, end));
  }

  print('Veri ${chunks.length} parçaya bölündü');

  // Her parçayı paralel olarak işleme
  final start = DateTime.now();

  final results = await Future.wait(
    chunks.map((chunk) => processDataChunk(chunk))
  );

  // Sonuçları birleştirme
  final combinedResults = results.expand((result) => result).toList();

  final end = DateTime.now();
  final duration = end.difference(start);

  print('İşlem tamamlandı, süre: ${duration.inMilliseconds} ms');
  print('İşlenen öğe sayısı: ${combinedResults.length}');
  print('Örnek sonuçlar: ${combinedResults.take(5)}');
}

Future<List<int>> processDataChunk(List<int> chunk) async {
  // Yoğun bir işlem simülasyonu
  await Future.delayed(Duration(milliseconds: 500));

  // Her sayının karesini alma
  return chunk.map((number) => number * number).toList();
}

Bu örnekte, büyük bir veri seti daha küçük parçalara ayrılır ve her parça paralel olarak işlenir. Bu yaklaşım, özellikle çok çekirdekli işlemcilerde performansı önemli ölçüde artırabilir.

Gerçek Dünya Senaryoları

Future.wait gerçek dünya uygulamalarında çeşitli senaryolarda kullanılabilir. İşte bazı yaygın kullanım örnekleri:

API İstekleri

Birden fazla API’den veri çekerken, istekleri paralel olarak çalıştırmak için Future.wait kullanabilirsiniz:

import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;

Future<void> main() async {
  try {
    // Birden fazla API isteğini paralel olarak çalıştırma
    final results = await Future.wait([
      fetchUser(1),
      fetchPosts(),
      fetchComments(1)
    ]);

    // Sonuçları ayrıştırma
    final user = results[0] as Map;
    final posts = results[1] as List<dynamic>;
    final comments = results[2] as List<dynamic>;

    // Sonuçları kullanma
    print('Kullanıcı: ${user['name']}');
    print('Gönderi sayısı: ${posts.length}');
    print('Yorum sayısı: ${comments.length}');

    // Verileri birleştirme
    final userData = {
      'user': user,
      'posts': posts,
      'comments': comments
    };

    print('Birleştirilmiş veri: ${json.encode(userData)}');
  } catch (e) {
    print('Veri çekme hatası: $e');
  }
}

Future<Map<String, dynamic>> fetchUser(int userId) async {
  final response = await http.get(
    Uri.parse('https://jsonplaceholder.typicode.com/users/$userId')
  );

  if (response.statusCode == 200) {
    return json.decode(response.body);
  } else {
    throw Exception('Kullanıcı verileri alınamadı');
  }
}

Future<List<dynamic>> fetchPosts() async {
  final response = await http.get(
    Uri.parse('https://jsonplaceholder.typicode.com/posts?_limit=5')
  );

  if (response.statusCode == 200) {
    return json.decode(response.body);
  } else {
    throw Exception('Gönderiler alınamadı');
  }
}

Future<List<dynamic>> fetchComments(int postId) async {
  final response = await http.get(
    Uri.parse('https://jsonplaceholder.typicode.com/comments?postId=$postId')
  );

  if (response.statusCode == 200) {
    return json.decode(response.body);
  } else {
    throw Exception('Yorumlar alınamadı');
  }
}

Bu örnekte, kullanıcı bilgileri, gönderiler ve yorumlar gibi farklı verileri paralel olarak çekiyoruz. Bu, özellikle bu verilerin birbirine bağımlı olmadığı durumlarda uygulamanın yükleme süresini önemli ölçüde azaltabilir.

Veritabanı İşlemleri

Veritabanı işlemlerini paralel olarak çalıştırmak için de Future.wait kullanabilirsiniz:

import 'dart:async';

// Veritabanı işlemleri için örnek sınıf
class Database {
  Future<Map<String, dynamic>> getUser(int userId) async {
    await Future.delayed(Duration(milliseconds: 800));
    return {
      'id': userId,
      'name': 'Kullanıcı $userId',
      'email': 'user$userId@example.com'
    };
  }

  Future<List<Map<String, dynamic>>> getUserOrders(int userId) async {
    await Future.delayed(Duration(milliseconds: 1200));
    return List.generate(3, (index) => {
      'id': index + 1,
      'userId': userId,
      'product': 'Ürün ${index + 1}',
      'price': (index + 1) * 100
    });
  }

  Future<Map<String, dynamic>> getUserSettings(int userId) async {
    await Future.delayed(Duration(milliseconds: 500));
    return {
      'userId': userId,
      'theme': 'dark',
      'notifications': true,
      'language': 'tr'
    };
  }

  Future<void> updateLastLogin(int userId) async {
    await Future.delayed(Duration(milliseconds: 300));
    print('Kullanıcı $userId için son giriş tarihi güncellendi');
  }
}

Future<void> main() async {
  final db = Database();
  final userId = 42;

  print('Kullanıcı verileri yükleniyor...');

  try {
    // Kullanıcı verilerini paralel olarak yükleme
    final results = await Future.wait([
      db.getUser(userId),
      db.getUserOrders(userId),
      db.getUserSettings(userId),
      db.updateLastLogin(userId)
    ]);

    // Sonuçları ayrıştırma (son öğe void olduğu için null)
    final user = results[0] as Map<String, dynamic>;
    final orders = results[1] as List<Map<String, dynamic>>;
    final settings = results[2] as Map<String, dynamic>;

    // Kullanıcı profilini oluşturma
    final userProfile = {
      ...user,
      'orders': orders,
      'settings': settings,
      'lastLogin': DateTime.now().toIso8601String()
    };

    print('Kullanıcı profili yüklendi:');
    print('İsim: ${userProfile['name']}');
    print('E-posta: ${userProfile['email']}');
    print('Sipariş sayısı: ${orders.length}');
    print('Tema: ${settings['theme']}');
  } catch (e) {
    print('Veritabanı işlemi hatası: $e');
  }
}

Bu örnekte, kullanıcı bilgileri, siparişler ve ayarlar gibi farklı veritabanı sorgularını paralel olarak çalıştırıyoruz. Bu, özellikle bu sorguların birbirine bağımlı olmadığı durumlarda uygulamanın yanıt verme süresini önemli ölçüde azaltabilir.

İleri Düzey Kullanım

Future.wait ile ilgili bazı ileri düzey kullanım senaryoları şunlardır:

Dinamik Future Listesi Oluşturma

Bazen, çalıştırılacak Future’ların sayısı önceden bilinmeyebilir. Bu durumda, dinamik olarak Future listesi oluşturabilirsiniz:

import 'dart:async';
import 'dart:math';

Future<void> main() async {
  // Dinamik olarak Future listesi oluşturma
  final itemCount = Random().nextInt(20);

  // Fonksiyon listesi oluşturma
  final List<Future<String> Function()> futureFactories = List.generate(
    itemCount,
    (index) => () => processItem(index)
  );

  print('Future fabrikaları oluşturuldu');

  // Tüm Future'ları başlatma ve bekleme
  final futures = futureFactories.map((factory) => factory()).toList();

  print('Tüm Future\'lar başlatıldı, sonuçlar bekleniyor...');

  final results = await Future.wait(futures);

  print('Tüm işlemler tamamlandı:');
  results.forEach(print);
}

Future<String> processItem(int index) async {
  // Her öğe için farklı bir işlem süresi
  final processingTime = (index + 1) * 500;
  await Future.delayed(Duration(milliseconds: processingTime));
  return 'Öğe $index işlendi (süre: ${processingTime}ms)';
}

Bu örnekte, işlenecek öğe sayısına göre dinamik olarak Future listesi oluşturuyoruz. Bu yaklaşım, özellikle çalıştırılacak Future’ların sayısı çalışma zamanında belirlendiğinde faydalıdır.

Future.wait ile Timeout Kullanımı

Future.wait ile birlikte timeout kullanarak, belirli bir süre içinde tamamlanmayan işlemleri iptal edebilirsiniz:

import 'dart:async';

Future<void> main() async {
  // Farklı sürelerde tamamlanan Future'lar
  Future<String> quickTask() async {
    await Future.delayed(Duration(seconds: 1));
    return 'Hızlı görev tamamlandı';
  }

  Future<String> mediumTask() async {
    await Future.delayed(Duration(seconds: 3));
    return 'Orta görev tamamlandı';
  }

  Future<String> slowTask() async {
    await Future.delayed(Duration(seconds: 5));
    return 'Yavaş görev tamamlandı';
  }

  // Timeout ile Future.wait kullanımı
  try {
    print('2 saniyelik timeout ile Future.wait başlatılıyor...');

    final results = await Future.wait([
      quickTask(),
      mediumTask(),
      slowTask()
    ]).timeout(Duration(seconds: 2));

    print('Sonuçlar: $results');
  } on TimeoutException {
    print('İşlem zaman aşımına uğradı!');
  }

  // Her Future için ayrı timeout kullanımı
  print('\nHer Future için ayrı timeout kullanımı:');

  final results = await Future.wait([
    quickTask().timeout(Duration(seconds: 2), onTimeout: () => 'Hızlı görev timeout'),
    mediumTask().timeout(Duration(seconds: 2), onTimeout: () => 'Orta görev timeout'),
    slowTask().timeout(Duration(seconds: 6), onTimeout: () => 'Yavaş görev timeout')
  ]);

  print('Sonuçlar:');
  results.forEach(print);
}

Bu örnekte, Future.wait ile birlikte timeout kullanarak, belirli bir süre içinde tamamlanmayan işlemleri yönetiyoruz. İlk durumda, tüm işlemler için genel bir timeout kullanıyoruz. İkinci durumda ise, her işlem için ayrı bir timeout kullanıyoruz.

Future.wait ile Retry Mekanizması

Bazen, özellikle ağ istekleri gibi güvenilmez işlemlerde, başarısız olan işlemleri yeniden denemeniz gerekebilir. İşte Future.wait ile birlikte bir retry mekanizması örneği:

import 'dart:async';
import 'dart:math';

Future<void> main() async {
  // Bazen başarısız olan görevler
  Future<String> unreliableTask1() async {
    await Future.delayed(Duration(milliseconds: 500));
    if (Random().nextBool()) {
      throw Exception('Görev 1 başarısız oldu');
    }
    return 'Görev 1 başarılı';
  }

  Future<String> unreliableTask2() async {
    await Future.delayed(Duration(milliseconds: 700));
    if (Random().nextBool()) {
      throw Exception('Görev 2 başarısız oldu');
    }
    return 'Görev 2 başarılı';
  }

  // Retry mekanizması ile Future'ları çalıştırma
  final results = await Future.wait([
    retryFuture(unreliableTask1, maxAttempts: 3),
    retryFuture(unreliableTask2, maxAttempts: 3)
  ]);

  print('Tüm görevler tamamlandı:');
  results.forEach(print);
}

Future<T> retryFuture<T>(
  Future<T> Function() futureFactory, {
  int maxAttempts = 3,
  Duration delay = const Duration(milliseconds: 500),
}) async {
  int attempts = 0;

  while (true) {
    attempts++;
    try {
      return await futureFactory();
    } catch (e) {
      if (attempts >= maxAttempts) {
        print('Maksimum deneme sayısına ulaşıldı ($maxAttempts), hata: $e');
        rethrow;
      }

      print('Deneme $attempts başarısız: $e. Yeniden deneniyor...');
      await Future.delayed(delay * attempts);
    }
  }
}

Bu örnekte, güvenilmez işlemleri yeniden denemek için bir yardımcı fonksiyon kullanıyoruz. Bu yaklaşım, özellikle ağ istekleri gibi geçici hatalar oluşabilecek işlemlerde faydalıdır.

Flutter UI ile Entegrasyon

Future.wait Flutter UI ile entegre edildiğinde, kullanıcı deneyimini önemli ölçüde iyileştirebilir. İşte bazı yaygın entegrasyon senaryoları:

Veri Yükleme Ekranı

Birden fazla veri kaynağından veri yüklerken, Future.wait kullanarak tüm verilerin paralel olarak yüklenmesini sağlayabilirsiniz:

import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;

class DataLoadingScreen extends StatefulWidget {
  @override
  _DataLoadingScreenState createState() => _DataLoadingScreenState();
}

class _DataLoadingScreenState extends State<DataLoadingScreen> {
  bool isLoading = true;
  String? errorMessage;
  Map<String, dynamic> userData = {};
  List<dynamic> userPosts = [];
  List<dynamic> userAlbums = [];

  @override
  void initState() {
    super.initState();
    loadUserData();
  }

  Future<void> loadUserData() async {
    setState(() {
      isLoading = true;
    });

    try {
      // Birden fazla API isteğini paralel olarak çalıştırma
      final results = await Future.wait([
        http.get(Uri.parse('https://jsonplaceholder.typicode.com/users/1')),
        http.get(Uri.parse('https://jsonplaceholder.typicode.com/users/1/posts')),
        http.get(Uri.parse('https://jsonplaceholder.typicode.com/users/1/albums'))
      ]);

      // Yanıtları kontrol etme
      if (results.any((response) => response.statusCode != 200)) {
        throw Exception('Veri yüklenirken bir hata oluştu');
      }

      // Verileri ayrıştırma
      setState(() {
        userData = json.decode(results[0].body);
        userPosts = json.decode(results[1].body);
        userAlbums = json.decode(results[2].body);
        isLoading = false;
      });
    } catch (e) {
      setState(() {
        errorMessage = 'Veri yüklenirken bir hata oluştu: $e';
        isLoading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Kullanıcı Profili'),
        actions: [
          IconButton(
            icon: Icon(Icons.refresh),
            onPressed: loadUserData,
          )
        ],
      ),
      body: isLoading
          ? Center(child: CircularProgressIndicator())
          : errorMessage != null
              ? Center(child: Text(errorMessage!, style: TextStyle(color: Colors.red)))
              : SingleChildScrollView(
                  padding: EdgeInsets.all(16.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      // Kullanıcı bilgileri
                      Card(
                        child: Padding(
                          padding: EdgeInsets.all(16.0),
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Text(
                                userData['name'] ?? '.........',
                                style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                              ),
                              SizedBox(height: 8),
                              Text('Email: ${userData['email'] ?? '...'}'),
                              Text('Telefon: ${userData['phone'] ?? '...'}'),
                              Text('Website: ${userData['website'] ?? '...'}'),
                            ],
                          ),
                        ),
                      ),

                      SizedBox(height: 16),

                      // Gönderiler
                      Text('Gönderiler', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
                      SizedBox(height: 8),
                      ...userPosts.take(3).map((post) => Card(
                        margin: EdgeInsets.only(bottom: 8),
                        child: Padding(
                          padding: EdgeInsets.all(12.0),
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Text(
                                post['title'] ?? '...',
                                style: TextStyle(fontWeight: FontWeight.bold),
                              ),
                              SizedBox(height: 4),
                              Text(post['body'] ?? '...'),
                            ],
                          ),
                        ),
                      )).toList(),

                      SizedBox(height: 16),

                      // Albümler
                      Text('Albümler', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
                      SizedBox(height: 8),
                      ...userAlbums.take(3).map((album) => Card(
                        margin: EdgeInsets.only(bottom: 8),
                        child: Padding(
                          padding: EdgeInsets.all(12.0),
                          child: Text(album['title'] ?? '...'),
                        ),
                      )).toList(),
                    ],
                  ),
                ),
    );
  }
}

Bu örnekte, kullanıcı profili, gönderiler ve albümler gibi farklı verileri paralel olarak yüklüyoruz. Yükleme sırasında bir yükleme göstergesi gösteriyoruz ve herhangi bir hata oluşursa kullanıcıya bildiriyoruz.

Çoklu Form Doğrulama

Birden fazla form alanını paralel olarak doğrulamak için Future.wait kullanabilirsiniz:

import 'package:flutter/material.dart';
import 'dart:async';

class MultiValidationForm extends StatefulWidget {
  @override
  _MultiValidationFormState createState() => _MultiValidationFormState();
}

class _MultiValidationFormState extends State<MultiValidationForm> {
  final _formKey = GlobalKey<FormState>();
  final _emailController = TextEditingController();
  final _usernameController = TextEditingController();
  final _passwordController = TextEditingController();

  bool _isValidating = false;
  String _validationMessage = "";

  @override
  void dispose() {
    _emailController.dispose();
    _usernameController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  Future<bool> validateEmail(String email) async {
    // E-posta doğrulama simülasyonu
    await Future.delayed(Duration(seconds: 1));
    return email.contains('@') && email.contains('.');
  }

  Future<bool> validateUsername(String username) async {
    // Kullanıcı adı doğrulama simülasyonu
    await Future.delayed(Duration(milliseconds: 800));
    return username.length >= 4 && !username.contains(' ');
  }

  Future<bool> validatePassword(String password) async {
    // Şifre doğrulama simülasyonu
    await Future.delayed(Duration(milliseconds: 600));
    return password.length >= 8 && 
           password.contains(RegExp(r'[A-Z]')) && 
           password.contains(RegExp(r'[0-9]'));
  }

  Future<void> validateForm() async {
    if (!_formKey.currentState!.validate()) {
      return;
    }

    setState(() {
      _isValidating = true;
      _validationMessage = "";
    });

    try {
      // Tüm doğrulamaları paralel olarak çalıştırma
      final results = await Future.wait([
        validateEmail(_emailController.text),
        validateUsername(_usernameController.text),
        validatePassword(_passwordController.text)
      ]);

      // Tüm doğrulamalar başarılı mı kontrol etme
      final allValid = results.every((isValid) => isValid);

      setState(() {
        _isValidating = false;
        _validationMessage = allValid
            ? 'Form başarıyla doğrulandı!'
            : 'Lütfen tüm alanları doğru formatta doldurun.\n\n=======\nLOG: \nEmail, Username, Password \n$results\n=======';
      });

      if (allValid) {
        // Form gönderme işlemi
        submitForm();
      }
    } catch (e) {
      setState(() {
        _isValidating = false;
        _validationMessage = 'Doğrulama sırasında bir hata oluştu: $e';
      });
    }
  }

  void submitForm() {
    // Form gönderme simülasyonu
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('Form başarıyla gönderildi!'))
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Çoklu Form Doğrulama'),
      ),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Form(
          key: _formKey,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              TextFormField(
                controller: _emailController,
                decoration: InputDecoration(
                  labelText: 'E-posta',
                  hintText: 'ornek@email.com',
                ),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Lütfen bir e-posta adresi girin';
                  }
                  return null;
                },
              ),
              SizedBox(height: 16),
              TextFormField(
                controller: _usernameController,
                decoration: InputDecoration(
                  labelText: 'Kullanıcı Adı',
                  hintText: 'En az 4 karakter',
                ),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Lütfen bir kullanıcı adı girin';
                  }
                  return null;
                },
              ),
              SizedBox(height: 16),
              TextFormField(
                controller: _passwordController,
                decoration: InputDecoration(
                  labelText: 'Şifre',
                  hintText: 'En az 8 karakter, 1 büyük harf ve 1 rakam',
                ),
                obscureText: true,
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Lütfen bir şifre girin';
                  }
                  return null;
                },
              ),
              SizedBox(height: 24),
              ElevatedButton(
                onPressed: _isValidating ? null : validateForm,
                child: _isValidating
                    ? Row(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          SizedBox(
                            width: 20,
                            height: 20,
                            child: CircularProgressIndicator(strokeWidth: 2),
                          ),
                          SizedBox(width: 12),
                          Text('Doğrulanıyor...'),
                        ],
                      )
                    : Text('Formu Doğrula'),
              ),
              SizedBox(height: 16),
              if (_validationMessage.isNotEmpty)
                Container(
                  padding: EdgeInsets.all(12),
                  color: _validationMessage.contains('başarıyla')
                      ? Colors.green.shade100
                      : Colors.red.shade100,
                  child: Text(
                    _validationMessage,
                    style: TextStyle(
                      color: _validationMessage.contains('başarıyla')
                          ? Colors.green.shade800
                          : Colors.red.shade800,
                    ),
                  ),
                ),
            ],
          ),
        ),
      ),
    );
  }
}

Bu örnekte, e-posta, kullanıcı adı ve şifre gibi farklı form alanlarını paralel olarak doğruluyoruz. Doğrulama sırasında bir yükleme göstergesi gösteriyoruz ve sonuçları kullanıcıya bildiriyoruz.

En İyi Uygulamalar ve İpuçları

Future.wait kullanırken aşağıdaki en iyi uygulamaları ve ipuçlarını göz önünde bulundurmanız önerilir:

1. Bağımsız İşlemleri Paralel Çalıştırın

Future.wait en çok, birbirine bağımlı olmayan işlemleri paralel olarak çalıştırmak istediğinizde faydalıdır. Eğer bir işlem diğerinin sonucuna bağımlıysa, bu işlemleri sıralı olarak çalıştırmanız gerekir.

2. Hata Yönetimine Dikkat Edin

Varsayılan olarak, Future.wait listedeki herhangi bir Future başarısız olursa hata fırlatır. Bu davranışı eagerError ve cleanUp parametreleriyle özelleştirebilirsiniz. Hataları düzgün bir şekilde yakalamak ve işlemek için her zaman try-catch bloklarını kullanın.

3. Performans Etkisini Göz Önünde Bulundurun

Future.wait işlemleri paralel olarak çalıştırır, ancak hepsi aynı isolate üzerinde çalışır. Bu, özellikle CPU yoğun işlemlerde performans sorunlarına neden olabilir. Gerçek paralellik için, ağır hesaplamalar için ayrı isolate’ler kullanmayı düşünün.

4. Timeout Kullanın

Uzun süren işlemler için timeout kullanarak, belirli bir süre içinde tamamlanmayan işlemleri iptal edebilirsiniz. Bu, özellikle ağ istekleri gibi dış kaynaklara bağımlı işlemlerde faydalıdır.

5. Dinamik Future Listeleri Oluşturun

Çalıştırılacak Future’ların sayısı önceden bilinmiyorsa, dinamik olarak Future listesi oluşturabilirsiniz. Bu, özellikle kullanıcı girdisine veya çalışma zamanı koşullarına bağlı olarak değişen işlem sayıları için faydalıdır.

6. UI ile Entegrasyonda Yükleme Durumunu Gösterin

Flutter UI ile entegre ederken, işlemlerin durumunu kullanıcıya göstermek için yükleme göstergeleri kullanın. Bu, kullanıcı deneyimini önemli ölçüde iyileştirir.

7. Büyük Veri Setlerini Parçalara Ayırın

Büyük veri setleriyle çalışırken, veriyi daha küçük parçalara ayırıp her parçayı paralel olarak işlemek için Future.wait kullanabilirsiniz. Bu, özellikle çok çekirdekli işlemcilerde performansı artırabilir.

Sonuç

Future.wait, Flutter’da birden fazla asenkron işlemi etkili bir şekilde yönetmek için güçlü bir araçtır. Birbirine bağımlı olmayan işlemleri paralel olarak çalıştırarak, uygulamanızın performansını önemli ölçüde artırabilirsiniz.

Bu makalede, Future.wait‘in temel kullanımından ileri düzey senaryolara kadar çeşitli örnekler gördük. Hata yönetimi, performans optimizasyonu, gerçek dünya senaryoları ve Flutter UI ile entegrasyon gibi konuları inceledik.

Future.wait‘i uygulamanızda kullanarak, kullanıcılarınıza daha hızlı ve daha duyarlı bir deneyim sunabilirsiniz. Ancak, her güçlü araçta olduğu gibi, Future.wait‘i doğru şekilde kullanmak için hata yönetimi, performans etkileri ve en iyi uygulamalar gibi konuları göz önünde bulundurmanız önemlidir.

Kaynaklar

“Flutter’da Future.wait Kullanımı: Asenkron İşlemleri Etkili Yönetme” için bir yanıt

  1. Volkan avatarı
    Volkan

    Çok bilgilendirici teşekkürler 🙏

Volkan için bir yanıt yazın Yanıtı iptal et

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir