↑↓ seç · Enter aç · Esc kapat

Mutlu Tekin
Mutlu Tekin
← Yazılar

Bölüm 5 — Konu 18: Future/Async-Await Flutter Bağlamında, FutureBuilder

3 dk okuma #flutter
Dizi · 18/61 Flutter Türkçe Tutorial
  1. Bölüm 1 — Konu 1: Flutter Nedir, Mimarisi (Widget → Element → RenderObject, Skia/Impeller)
  2. Bölüm 1 — Konu 2: Ortam Kurulumu (Flutter SDK, Android Studio/VS Code, Emulator/Simulator, DevTools'a İlk Bakış)
  3. Bölüm 1 — Konu 3: İlk Proje Yapısı (`pubspec.yaml`, `lib/`, Klasör Mimarisi, `flutter create` Anatomisi)
  4. Bölüm 1 — Konu 4: Hot Reload / Hot Restart Ne Yapıyor, Neden Önemli
  5. Bölüm 2 — Konu 5: "Her Şey Widget'tır" Felsefesi, Widget Ağacı
  6. Bölüm 2 — Konu 6: StatelessWidget vs StatefulWidget + `setState()` Derinlemesine (Rebuild Mekanizması)
  7. Bölüm 2 — Konu 7: Temel Layout — Container, Row, Column, Stack, Padding, Align, Center
  8. Bölüm 2 — Konu 8: Constraint Sistemi — "Constraints Go Down, Sizes Go Up, Parent Sets Position"
  9. Bölüm 2 — Konu 9: `Expanded`, `Flexible`, `Spacer`, Intrinsic Widget'lar
  10. Bölüm 3 — Konu 10: MaterialApp/CupertinoApp, Scaffold, AppBar
  11. Bölüm 3 — Konu 11: `ListView`, `GridView`, `SingleChildScrollView` (+ Builder Pattern)
  12. Bölüm 3 — Konu 12: Text, TextStyle, Icon, Image, Asset Yönetimi
  13. Bölüm 3 — Konu 13: Custom Widget Yazma Prensipleri (Composition Over Inheritance)
  14. Bölüm 3 — Konu 14: Navigator 1.0 — Push/Pop, Named Routes
  15. Bölüm 4 — Konu 15: Form, TextField/TextFormField, GlobalKey<FormState>, Validasyon
  16. Bölüm 4 — Konu 16: Theme Sistemi (ThemeData, ColorScheme)
  17. Bölüm 4 — Konu 17: Responsive & Adaptive Tasarım (MediaQuery, LayoutBuilder, OrientationBuilder, Breakpoint Stratejileri)
  18. Bölüm 5 — Konu 18: Future/Async-Await Flutter Bağlamında, FutureBuilder
  19. Bölüm 5 — Konu 19: `Stream`, `StreamBuilder`
  20. Bölüm 5 — Konu 20: HTTP İstekleri (`http` / `dio` Paketleri)
  21. Bölüm 5 — Konu 21: JSON Serialization (Manuel → `json_serializable`/`freezed`)
  22. Bölüm 5 — Konu 22: Local Storage — `shared_preferences` → `Hive` → `sqflite`/Drift
  23. Bölüm 6 — Konu 23: InheritedWidget ve InheritedModel — "Neden setState Yetmiyor" Sorusunun Cevabı
  24. Bölüm 6 — Konu 24: Provider Paketi
  25. Bölüm 6 — Konu 25: Riverpod (Modern Yaklaşım, Provider'ın Halefi)
  26. Bölüm 6 — Konu 26: BLoC/Cubit Pattern (`flutter_bloc`)
  27. Bölüm 6 — Konu 27: GetX (Tartışmalı Ama Yaygın)
  28. Bölüm 6 — Konu 28: Karşılaştırma — Hangi Projede Hangisi?
  29. Bölüm 7 — Konu 29: Navigator 2.0 (Router, RouteInformationParser, RouterDelegate)
  30. Bölüm 7 — Konu 30: `go_router` Paketi (Pratik ve Modern Çözüm)
  31. Bölüm 7 — Konu 31: Deep Linking
  32. Bölüm 7 — Konu 32: Repository Pattern, Katmanlı Mimari (Data/Domain/Presentation)
  33. Bölüm 7 — Konu 33: Dependency Injection (`get_it`, `injectable`)
  34. Bölüm 7 — Konu 34: Clean Architecture Uyarlaması, SOLID Prensipleri
  35. Bölüm 8 — Konu 35: Implicit Animasyonlar
  36. Bölüm 8 — Konu 36: Explicit Animasyonlar (`AnimationController`, `Tween`, `Curve`)
  37. Bölüm 8 — Konu 37: Hero Animasyonları
  38. Bölüm 8 — Konu 38: `CustomPainter` / `Canvas`
  39. Bölüm 8 — Konu 39: Rive / Lottie Entegrasyonu
  40. Bölüm 9 — Konu 40: Platform Channels
  41. Bölüm 9 — Konu 41: Permission Yönetimi (`permission_handler`)
  42. Bölüm 9 — Konu 42: Kamera, Konum, Sensörler
  43. Bölüm 9 — Konu 43: Push Notification (Firebase Cloud Messaging)
  44. Bölüm 9 — Konu 44: Android/iOS Build Sistemleri
  45. Bölüm 10 — Konu 45: Test Yazımı (Unit, Widget, Integration, Golden)
  46. Bölüm 10 — Konu 46: CI/CD
  47. Bölüm 10 — Konu 47: Rebuild Optimizasyonu (`const`, `key` Kullanımı)
  48. Bölüm 10 — Konu 48: DevTools Profiling
  49. Bölüm 10 — Konu 49: Lazy Loading, Pagination, Büyük Liste Performansı
  50. Bölüm 11 — Konu 50: Firebase Ekosistemi (Auth, Firestore, Storage, Functions)
  51. Bölüm 11 — Konu 51: Supabase Alternatifi
  52. Bölüm 11 — Konu 52: GraphQL (Opsiyonel)
  53. Bölüm 11 — Konu 53: Store Yayınlama Süreci (İmzalama, Listing, Versiyonlama)
  54. Bölüm 11 — Konu 54: App Size, Obfuscation, Flavor Yönetimi
  55. Bölüm 12 — Konu 55: Flutter Web / Desktop
  56. Bölüm 12 — Konu 56: Custom `RenderObject` Yazımı
  57. Bölüm 12 — Konu 57: Engine Mimarisi Derinlemesine (Impeller vs Skia)
  58. Bölüm 12 — Konu 58: Plugin Geliştirme, pub.dev'e Paket Yayınlama
  59. Bölüm 12 — Konu 59: Monorepo Mimarisi (Melos)
  60. Bölüm 12 — Konu 60: Erişilebilirlik (Accessibility) Derinlemesine
  61. Bölüm 12 — Konu 61: Yerelleştirme (Localization / i18n)
Dizinin sayfası →
İçindekiler 9 başlık
  1. Sorun: Asenkron Veri, Senkron UI
  2. FutureBuilder — Çözüm
  3. AsyncSnapshot — Future'ın "O Anki Durumunun" Fotoğrafı
  4. ConnectionState — Bir Enum, Dart Bölüm 7'yi Hatırla
  5. Gerçek Bir Örnek — Simüle Edilmiş API Çağrısı
  6. Kritik Bir Tuzak — future: Parametresini build() İçinde Doğrudan Çağırmak
  7. ?? ile Güvenli Varsayılan Değer
  8. 🎯 Bu Dersten Çıkarılması Gerekenler
  9. 📝 Ödevler

Bölüm 5'e hoş geldin — burada Dart Bölüm 10'da öğrendiğin Future/async/await bilgisi doğrudan işine yarayacak. Yeni bir kavram öğrenmiyorsun, var olan bilgini Flutter'ın UI dünyasına uyguluyorsun.

Sorun: Asenkron Veri, Senkron UI

Dart Bölüm 10'da, bir Future'ın sonucunun gelmesini beklerken, programın donmadığını öğrenmiştik. Ama Flutter'da bir problem var: build() metodu (Bölüm 2 Konu 5'i hatırla), senkron çalışmak zorundadır — build() içinde await kullanamazsın.

dart
@override
Widget build(BuildContext context) {
  final veri = await birApiCagrisi(); // ❌ HATA! build() async olamaz
  return Text(veri);
}

Neden olmaz? build(), Flutter'ın belirli bir anda, senkron olarak çağırdığı bir metoddur — Bölüm 1 Konu 1'de öğrendiğimiz RenderObject ağacının, her frame'de tutarlı bir şekilde güncellenmesi gerekir. Eğer build() "bekleyebilseydi", Flutter'ın ne zaman ekranı çizeceğini bilmesi imkansız hale gelirdi.

FutureBuilder — Çözüm

dart
FutureBuilder<String>(
  future: birApiCagrisi(),
  builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return CircularProgressIndicator(); // yükleniyor
    } else if (snapshot.hasError) {
      return Text('Hata: ${snapshot.error}');
    } else {
      return Text('Veri: ${snapshot.data}');
    }
  },
)

FutureBuilder, Bölüm 3 Konu 11'de öğrendiğimiz builder pattern'in bir başka uygulamasıdır — ama bu sefer, builder fonksiyonu birden fazla kez çağrılır: bir kere Future beklerken, bir kere daha Future tamamlandığında (Flutter, arka planda bunu senin için setState() benzeri bir mekanizmayla yönetir).

AsyncSnapshot — Future'ın "O Anki Durumunun" Fotoğrafı

Dart Bölüm 10'da bir Future'ın üç olası durumu olduğunu öğrenmiştik (henüz tamamlanmadı / başarıyla tamamlandı / hata ile tamamlandı). AsyncSnapshot<T>, Dart Bölüm 9'da öğrendiğimiz generic bir sınıf olarak, bu üç durumu tek bir nesnede kapsüller:

dart
snapshot.connectionState  // ConnectionState.none / waiting / active / done
snapshot.hasData          // veri geldi mi? (bool)
snapshot.data             // gelen veri (T?, nullable!)
snapshot.hasError         // hata oluştu mu? (bool)
snapshot.error            // oluşan hata (Object?, nullable!)

Dikkat et: snapshot.data, nullable'dır (Dart Bölüm 2'yi hatırla) — çünkü Future henüz tamamlanmamışsa, henüz bir veri yok. Bu yüzden snapshot.data kullanmadan önce, connectionState veya hasData ile kontrol etmen gerekir — tıpkı Bölüm 2'de öğrendiğimiz null safety pratiklerinde olduğu gibi.

ConnectionState — Bir Enum, Dart Bölüm 7'yi Hatırla

dart
enum ConnectionState { none, waiting, active, done }

Bu, Dart Bölüm 7'de öğrendiğimiz enum'un, Flutter'ın kendi kütüphanesinde tanımlanmış bir örneğidir:

  • none: henüz bir future verilmedi.
  • waiting: future çalışıyor, henüz tamamlanmadı.
  • done: future tamamlandı (başarılı veya hatalı).

FutureBuilder ile en sık waiting ve done durumlarını kontrol edersin.

Gerçek Bir Örnek — Simüle Edilmiş API Çağrısı

dart
class VeriEkrani extends StatelessWidget {
  const VeriEkrani({super.key});

  Future<String> _veriGetir() async {
    await Future.delayed(Duration(seconds: 2)); // Dart Bölüm 10'u hatırla
    return 'Sunucudan gelen veri';
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Veri Ekranı')),
      body: Center(
        child: FutureBuilder<String>(
          future: _veriGetir(),
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              return CircularProgressIndicator();
            }
            if (snapshot.hasError) {
              return Text('Bir hata oluştu: ${snapshot.error}');
            }
            return Text(snapshot.data!); // done durumunda, data artık null değil
          },
        ),
      ),
    );
  }
}

Bu kod çalıştığında: 2 saniye boyunca bir yükleme göstergesi (CircularProgressIndicator) görünür, sonra otomatik olarak "Sunucudan gelen veri" metnine geçer — kullanıcı hiçbir şey yapmadan.

Kritik Bir Tuzak — future: Parametresini build() İçinde Doğrudan Çağırmak

dart
// ❌ TEHLİKELİ pratik
class VeriEkraniYanlis extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FutureBuilder<String>(
      future: _veriGetir(), // HER rebuild'de YENİDEN çağrılır!
      builder: (context, snapshot) { ... },
    );
  }
}

Bölüm 2 Konu 6'da öğrendiğimiz build()'in her rebuild'de tekrar çalıştığını hatırlarsan — bu, ciddi bir soruna yol açıyor: eğer bu widget'ı içeren bir üst widget herhangi bir sebeple setState() çağırırsa (Bölüm 2'yi hatırla), build() tekrar çalışır, ve _veriGetir() her seferinde yeniden çağrılır — yani API isteği gereksiz yere tekrar tekrar atılır!

Doğru pratik — Future'ı StatefulWidget'ın state'inde saklamak:

dart
class VeriEkraniDogru extends StatefulWidget {
  const VeriEkraniDogru({super.key});

  @override
  State<VeriEkraniDogru> createState() => _VeriEkraniDogruState();
}

class _VeriEkraniDogruState extends State<VeriEkraniDogru> {
  late final Future<String> _veriFuture; // late final — Dart Bölüm 8'i hatırla!

  @override
  void initState() {
    super.initState();
    _veriFuture = _veriGetir(); // SADECE BİR KERE çağrılır
  }

  Future<String> _veriGetir() async {
    await Future.delayed(Duration(seconds: 2));
    return 'Sunucudan gelen veri';
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<String>(
      future: _veriFuture, // her rebuild'de AYNI future kullanılıyor
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return CircularProgressIndicator();
        }
        return Text(snapshot.data ?? 'Veri yok');
      },
    );
  }
}

late final Future<String> _veriFuture — Dart Bölüm 8 Konu 40'ta öğrendiğimiz late'in lazy initialization dışındaki asıl kullanım senaryosunu görüyorsun: alan, constructor'da değil, initState() içinde (Bölüm 2 Konu 6'da kısaca gördüğümüz, bir kere çalışan metod) atanıyor, ama non-nullable kalıyor. initState(), sadece bir kere çağrıldığı için, _veriGetir() de sadece bir kere çalışıyor — build() ne kadar tekrar çalışırsa çalışsın, aynı Future nesnesi kullanılıyor.

?? ile Güvenli Varsayılan Değer

dart
Text(snapshot.data ?? 'Veri yok')

Dart Bölüm 2/3'te öğrendiğimiz ?? operatörü, burada çok yaygın bir kalıptır — snapshot.data nullable olduğu için, ! ile zorlamak yerine (riskli, Bölüm 2'yi hatırla), ?? ile güvenli bir varsayılan sağlamak daha iyi bir pratiktir.


🎯 Bu Dersten Çıkarılması Gerekenler

  • build() senkron çalışmak zorundadır, await kullanamaz — FutureBuilder, bir Future'ın durumuna göre farklı UI göstermeyi sağlar.
  • AsyncSnapshot, bir Future'ın o anki durumunu (waiting/done, data, error) kapsülleyen generic bir sınıftır.
  • snapshot.data/snapshot.error nullable'dır — kullanmadan önce connectionState veya hasData/hasError ile kontrol edilmelidir.
  • Kritik tuzak: future: parametresine build() içinde doğrudan bir fonksiyon çağrısı vermek, her rebuild'de gereksiz yeniden çağrılara yol açar — Future'ı initState() içinde, late final bir alanda saklamak doğru çözümdür.

📝 Ödevler

  • [ ] Future.delayed ile sahte bir veri çeken, FutureBuilder ile loading/error/success durumlarını yöneten bir ekran yaz.
  • [ ] Bilerek future: parametresine doğrudan bir fonksiyon çağrısı ver, üst widget'ta bir setState() tetikleyerek API çağrısının tekrar tekrar yapıldığını (bir print ile sayarak) kanıtla.
  • [ ] Aynı sorunu, initState() + late final kalıbıyla düzelt, artık sadece bir kere çağrıldığını doğrula.
  • [ ] FutureBuilder'da bilerek bir hata fırlatan bir future kullan (throw Exception(...)), snapshot.hasError ile bunu yakalayıp kullanıcıya göster.
  • [ ] ConnectionState'in farklı durumlarını (waiting, done) print ile takip ederek, FutureBuilder'ın builder fonksiyonunun kaç kez çağrıldığını gözlemle.

Sıradaki konu: Bölüm 5 — Konu 19: Stream, StreamBuilder