↑↓ seç · Enter aç · Esc kapat

Mutlu Tekin
Mutlu Tekin
← Yazılar

Bölüm 5 — Konu 19: `Stream`, `StreamBuilder`

3 dk okuma #flutter
Dizi · 19/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. StreamBuilder — FutureBuilder'ın Kuzeni
  2. Temel Fark: builder, Kaç Kere Çağrılır?
  3. Neden Bu Kadar Değerli? — setState() ile Karşılaştırma
  4. Gerçek Dünya Senaryosu — Canlı Sayaç
  5. Stream Aynı Future'daki Gibi Tuzağa Dikkat
  6. Broadcast Stream ile Kullanım — Dart Bölüm 10'u Hatırlama
  7. FutureBuilder vs StreamBuilder — Ne Zaman Hangisi?
  8. 🎯 Bu Dersten Çıkarılması Gerekenler
  9. 📝 Ödevler

Bir önceki derste FutureBuilder'ı ("bir kerelik" asenkron veri için) öğrendik. Bu derste, Dart Bölüm 10'da öğrendiğimiz Stream ("zamanla, tekrar tekrar gelen veri") kavramının Flutter karşılığını işleyeceğiz.

StreamBuilder — FutureBuilder'ın Kuzeni

dart
StreamBuilder<int>(
  stream: sayacStream(),
  builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return CircularProgressIndicator();
    }
    if (snapshot.hasError) {
      return Text('Hata: ${snapshot.error}');
    }
    return Text('Değer: ${snapshot.data}');
  },
)

Fark hemen görülüyor: StreamBuilder, future: yerine stream: parametresi alıyor — ama builder fonksiyonunun imzası birebir aynı (AsyncSnapshot<T> kullanıyor, Bölüm 5 Konu 18'i hatırla). Bu, Flutter'ın API tasarımında tutarlılığı ne kadar önemsediğini gösteriyor — bir kere FutureBuilder'ı öğrendiğinde, StreamBuilder'ı öğrenmek neredeyse ücretsiz oluyor.

Temel Fark: builder, Kaç Kere Çağrılır?

Dart Bölüm 10 Konu 47'de Stream'in zamanla birden fazla değer ürettiğini öğrenmiştik. StreamBuilder'ın builder fonksiyonu, bu yüzden her yeni veri geldiğinde tekrar çağrılır:

dart
Stream<int> sayacStream() async* {
  for (int i = 1; i <= 5; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i;
  }
}

Bu stream'i StreamBuilder ile dinlediğinde, ekranda sırasıyla 1, 2, 3, 4, 5 görürsün — her yield gerçekleştiğinde, StreamBuilder otomatik olarak widget'ı yeniden çizer (Bölüm 2 Konu 6'da öğrendiğimiz setState()'e benzer bir mekanizmayı, framework senin için otomatik olarak yönetiyor).

Neden Bu Kadar Değerli? — setState() ile Karşılaştırma

StreamBuilder olmadan, aynı işi yapmak isteseydin:

dart
// ❌ Elle yönetim — çok daha karmaşık
class SayacWidget extends StatefulWidget {
  @override
  State<SayacWidget> createState() => _SayacWidgetState();
}

class _SayacWidgetState extends State<SayacWidget> {
  int? _deger;
  late StreamSubscription _subscription;

  @override
  void initState() {
    super.initState();
    _subscription = sayacStream().listen((veri) {
      setState(() {
        _deger = veri;
      });
    });
  }

  @override
  void dispose() {
    _subscription.cancel(); // Bölüm 5 Konu 20'de dispose'u derinlemesine göreceğiz
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Text('Değer: ${_deger ?? "Bekleniyor"}');
  }
}

Bu, çalışır ama çok daha fazla kod ve elle yönetilmesi gereken kaynaklar (_subscription.cancel()'ı unutursan, Dart Bölüm 10'da öğrendiğimiz stream kaynak sızıntısı riski) içeriyor. StreamBuilder, bunların hepsini otomatik olarak yapar — widget ekrandan kaldırıldığında, aboneliği kendisi iptal eder.

Gerçek Dünya Senaryosu — Canlı Sayaç

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

  Stream<int> _sayacStream() async* {
    int sayi = 0;
    while (true) {
      await Future.delayed(Duration(seconds: 1));
      yield sayi++;
    }
  }

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<int>(
      stream: _sayacStream(),
      initialData: 0, // stream'den ilk veri gelene kadar gösterilecek değer
      builder: (context, snapshot) {
        return Text(
          '${snapshot.data}',
          style: TextStyle(fontSize: 48),
        );
      },
    );
  }
}

initialData parametresi — Dart Bölüm 5'te öğrendiğimiz varsayılan parametre kavramına benzer bir mantıkla çalışır: stream henüz hiç veri üretmemişse (ilk yield'den önce), snapshot.data yerine bu başlangıç değeri kullanılır. Bu, FutureBuilder'da olmayan, StreamBuilder'a özgü kullanışlı bir parametredir — çünkü stream'in "hiç veri üretmeden bekleme" süresi genelde daha belirsiz olabilir.

Stream Aynı Future'daki Gibi Tuzağa Dikkat

Bölüm 5 Konu 18'de öğrendiğimiz kritik tuzağı hatırla — stream: parametresine build() içinde doğrudan bir fonksiyon çağrısı vermek, burada da aynı sorunu yaratır:

dart
// ❌ TEHLİKELİ — her rebuild'de YENİ bir stream başlatılır
StreamBuilder<int>(
  stream: _sayacStream(), // her build() çağrısında yeni bir stream!
  builder: (context, snapshot) => Text('${snapshot.data}'),
)

Doğru çözüm, aynı kalıp:

dart
class CanliSayacDogru extends StatefulWidget {
  @override
  State<CanliSayacDogru> createState() => _CanliSayacDogruState();
}

class _CanliSayacDogruState extends State<CanliSayacDogru> {
  late final Stream<int> _stream; // Bölüm 5 Konu 18'deki AYNI kalıp

  @override
  void initState() {
    super.initState();
    _stream = _sayacStream(); // sadece bir kere oluşturulur
  }

  Stream<int> _sayacStream() async* {
    int sayi = 0;
    while (true) {
      await Future.delayed(Duration(seconds: 1));
      yield sayi++;
    }
  }

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<int>(
      stream: _stream,
      builder: (context, snapshot) => Text('${snapshot.data ?? 0}'),
    );
  }
}

Broadcast Stream ile Kullanım — Dart Bölüm 10'u Hatırlama

Dart Bölüm 10 Konu 48'de öğrendiğimiz broadcast stream'i hatırlarsan — eğer aynı stream'i birden fazla StreamBuilder (farklı widget'larda) dinleyecekse, o stream'in broadcast olması gerekir:

dart
class BildirimServisi {
  final StreamController<String> _controller = StreamController<String>.broadcast();
  Stream<String> get bildirimler => _controller.stream;

  void bildirimGonder(String mesaj) {
    _controller.add(mesaj);
  }
}

Bu, gerçek uygulamalarda birden fazla ekranın aynı veri kaynağını (örneğin bir "okunmamış mesaj sayısı" göstergesi, hem ana ekranda hem ayarlar ekranında) dinlemesi gerektiğinde kullanılan bir kalıptır — Bölüm 6'da state management konusuna geldiğimizde, bu kalıbın daha gelişmiş versiyonlarını (Provider, Riverpod gibi araçlarla) göreceğiz.

FutureBuilder vs StreamBuilder — Ne Zaman Hangisi?

Senaryo Kullan
Bir API'den tek bir veri çekmek FutureBuilder
Gerçek zamanlı, sürekli güncellenen veri (sohbet mesajları, canlı skor, sensör verisi) StreamBuilder
Firestore gibi bir veritabanından canlı veri dinlemek StreamBuilder (bunu Bölüm 11'de Firebase konusunda göreceğiz)

🎯 Bu Dersten Çıkarılması Gerekenler

  • StreamBuilder, FutureBuilder ile aynı AsyncSnapshot<T> API'sini kullanır, ama builder, stream her yeni veri ürettiğinde tekrar çağrılır.
  • StreamBuilder, elle listen()/setState()/cancel() yönetiminin yerine geçer — abonelik yönetimini otomatik yapar.
  • initialData, stream ilk veriyi üretene kadar gösterilecek varsayılan değeri belirtir.
  • stream: parametresine build() içinde doğrudan fonksiyon çağrısı vermek, FutureBuilder'daki gibi bir tuzaktır — initState() + late final ile çözülür.
  • Birden fazla widget aynı stream'i dinleyecekse, stream'in broadcast olması gerekir (Dart Bölüm 10'u hatırla).

📝 Ödevler

  • [ ] Stream.periodic veya async*/yield ile her saniye artan bir sayaç gösteren StreamBuilder örneği yaz.
  • [ ] initialData kullanarak, stream ilk veriyi üretmeden önce gösterilecek bir başlangıç değeri belirle.
  • [ ] Bilerek stream: parametresine doğrudan fonksiyon çağrısı vererek tuzağa düş, sonra initState() + late final ile düzelt.
  • [ ] Broadcast bir StreamController oluştur, aynı stream'i iki farklı StreamBuilder widget'ında dinle.
  • [ ] Kendi cümlelerinle, "FutureBuilder ile StreamBuilder'ı ne zaman kullanmalıyım" sorusunu, Dart Bölüm 10'da öğrendiğin Future/Stream farkına dayanarak açıkla.

Sıradaki konu: Bölüm 5 — Konu 20: HTTP İstekleri (http / dio Paketleri)