↑↓ seç · Enter aç · Esc kapat

Mutlu Tekin
Mutlu Tekin
← Yazılar

Bölüm 10 — Konu 48: `StreamController`, Broadcast Stream

3 dk okuma #dart
Dizi · 47/64 Dart Türkçe Tutorial
  1. Bölüm 1 — Konu 1: Dart Nedir, Nerede Kullanılır, Neden Flutter Bu Dili Seçti
  2. Bölüm 1 — Konu 2: Ortam Kurulumu (Dart SDK, DartPad, Terminal ile Çalıştırma)
  3. Bölüm 1 — Konu 3: İlk Program (`main()`, `print()`, Dosya Yapısı)
  4. Bölüm 1 — Konu 4: Yorum Satırları, Temel Sözdizimi Kuralları
  5. Bölüm 2 — Konu 5: Değişken Tanımlama — `var`, `final`, `const` Farkı
  6. Bölüm 2 — Konu 6: Temel Tipler — `int`, `double`, `String`, `bool`
  7. Bölüm 2 — Konu 7: Tip Çıkarımı (Type Inference) ve Açık Tip Belirtme
  8. Bölüm 2 — Konu 8: `dynamic` ve `Object` — Ne Zaman Kullanılır, Ne Zaman Kaçınılır
  9. Bölüm 2 — Konu 9: Null Safety Temelleri (`?`, `!`, `late`'e Giriş)
  10. Bölüm 3 — Konu 10: Aritmetik, Atama, Karşılaştırma, Mantıksal Operatörler
  11. Bölüm 3 — Konu 11: `if / else if / else`
  12. Bölüm 3 — Konu 12: `switch` / `switch expression` (Modern Dart)
  13. Bölüm 3 — Konu 13: Ternary Operatör, `??`, `??=`, `?.`
  14. Bölüm 3 — Konu 14: `for`, `while`, `do-while` Döngüleri
  15. Bölüm 4 — Konu 16: `List` — Oluşturma, Erişim, Temel Metodlar
  16. Bölüm 4 — Konu 17: `Set` — Benzersiz Eleman Mantığı
  17. Bölüm 4 — Konu 18: `Map` — Key-Value Yapılar
  18. Bölüm 4 — Konu 19: Koleksiyon Üzerinde `for-in`, `forEach`
  19. Bölüm 4 — Konu 20: Spread Operatörü (`...`, `...?`) ve Collection If/For
  20. Bölüm 5 — Konu 21: Fonksiyon Tanımlama, Parametreler (Positional, Named, Optional)
  21. Bölüm 5 — Konu 22: Varsayılan Parametre Değerleri (Derinlemesine)
  22. Bölüm 5 — Konu 23: Arrow Function (`=>`) Sözdizimi
  23. Bölüm 5 — Konu 24: Fonksiyonlar Birinci Sınıf Vatandaş — Değişkene Atama, Parametre Olarak Geçme
  24. Bölüm 5 — Konu 25: Anonim Fonksiyonlar ve Closure Kavramı (Derinlemesine)
  25. Bölüm 5 — Konu 26: Recursion (Özyineleme)
  26. Bölüm 6 — Konu 27: Class Tanımlama, Constructor (Varsayılan, Named, Factory)
  27. Bölüm 6 — Konu 28: Alanlar (Fields), Metodlar, `this` Kullanımı (Derinlemesine)
  28. Bölüm 6 — Konu 29: Initializer List, Constructor Kısayolları (Derinlemesine)
  29. Bölüm 6 — Konu 30: Getter / Setter
  30. Bölüm 6 — Konu 31: Statik Üyeler (`static`)
  31. Bölüm 7 — Konu 32: Kalıtım (`extends`), `super` Kullanımı
  32. Bölüm 7 — Konu 33: Metod Override Etme, `@override`
  33. Bölüm 7 — Konu 34: Soyut Sınıflar (`abstract class`)
  34. Bölüm 7 — Konu 35: Interface Mantığı (`implements`)
  35. Bölüm 7 — Konu 36: Mixin (`with`)
  36. Bölüm 7 — Konu 37: `enum` — Basit ve Gelişmiş (Metotlu Enum'lar)
  37. Bölüm 8 — Konu 38: `try / catch / finally`, `throw`
  38. Bölüm 8 — Konu 39: Özel Exception Sınıfları Yazma
  39. Bölüm 8 — Konu 40: Sound Null Safety Derinlemesine — `late`, `required`, Non-Nullable Varsayılan Davranış
  40. Bölüm 8 — Konu 41: `assert` ile Geliştirme Zamanı Kontrolleri
  41. Bölüm 9 — Konu 42: Generic Sınıflar ve Fonksiyonlar
  42. Bölüm 9 — Konu 43: Generic Sınırlamalar (`<T extends ...>`)
  43. Bölüm 9 — Konu 44: Dart'ın Built-in Generic Koleksiyonları Nasıl Çalışır
  44. Bölüm 10 — Konu 45: `Future` Kavramı, `async`/`await`
  45. Bölüm 10 — Konu 46: `Future.then`, Hata Yönetimi (`catchError`)
  46. Bölüm 10 — Konu 47: `Stream` Temelleri, `async*`, `yield`
  47. Bölüm 10 — Konu 48: `StreamController`, Broadcast Stream
  48. Bölüm 11 — Konu 49: `map`, `where`, `reduce`, `fold`
  49. Bölüm 11 — Konu 50: `sort`, Custom Comparator ile Sıralama
  50. Bölüm 11 — Konu 51: Iterable Kavramı Derinlemesine (Lazy Evaluation)
  51. Bölüm 11 — Konu 52: Cascade Notasyonu (`..`)
  52. Bölüm 12 — Konu 53: `pubspec.yaml`, pub.dev'den Paket Ekleme
  53. Bölüm 12 — Konu 54: Kütüphane Oluşturma, `import`/`export`, `part`/`part of`
  54. Bölüm 12 — Konu 55: Dart'ta Test Yazımı (`test` Paketi, Unit Test Mantığı)
  55. Bölüm 12 — Konu 56: Extension Methods
  56. Bölüm 12 — Konu 57: Dart'ın Derleme Modelleri (JIT vs AOT) ve Bunun Flutter'a Etkisi
  57. Bölüm 13 — Konu 59: Dart 3 Pattern Matching — Records, Destructuring, Sealed Classes, Exhaustive Switch
  58. Bölüm 13 — Konu 60: `Never` Tipi, `covariant` ve Generic Variance Detayları
  59. Bölüm 13 — Konu 61: Event Loop Derinlemesine — Microtask Queue vs Event Queue
  60. Bölüm 13 — Konu 62: Memory Model & Performans — Garbage Collection, `const` Nesnelerin Bellek Avantajı
  61. Bölüm 13 — Konu 63: Isolate'ler Arası Mesajlaşmanın Maliyeti ve `compute()`'un İç İşleyişi
  62. Bölüm 13 — Konu 64: FFI (Foreign Function Interface) — C Koduna Erişim
  63. Bölüm 13 — Konu 65: Sunucu Tarafında Dart — `dart:io`, `shelf` Paketiyle Basit Bir Backend/CLI Aracı Yazma
  64. Bölüm 13 — Konu 66: Derleyici & Analiz Araçları — `dart analyze`, Custom Lint Kuralları, `build_runner` Mimarisi
Dizinin sayfası →
İçindekiler 10 başlık
  1. StreamController Nedir?
  2. StreamController'ın Parçaları
  3. Neden StreamController Kullanılır? — Gerçek Dünya Senaryosu
  4. Tek Dinleyicili (Single-Subscription) vs Broadcast Stream
  5. Broadcast Stream — Birden Fazla Dinleyici
  6. Broadcast Stream'in Önemli Bir Farkı — Geç Katılan Dinleyiciler
  7. close() — Kaynak Yönetimi
  8. 🎯 Bu Dersten Çıkarılması Gerekenler
  9. 📝 Ödevler
  10. 🏁 Bölüm 10 Tamamlandı!

Bölüm 10'un son konusundayız. Bir önceki derste async*/yield ile stream üretmeyi gördük — bu, "otomatik" bir stream oluşturma yoluydu. Bu derste, bir stream'e elle, istediğin an veri eklemeni sağlayan StreamController'ı işleyeceğiz.

StreamController Nedir?

dart
import 'dart:async';

void main() {
  StreamController<int> controller = StreamController<int>();

  controller.stream.listen((veri) {
    print("Alınan veri: $veri");
  });

  controller.add(1); // stream'e elle veri ekliyoruz
  controller.add(2);
  controller.add(3);

  controller.close(); // stream'i kapatıyoruz
}

Çıktı:

metin
Alınan veri: 1
Alınan veri: 2
Alınan veri: 3

StreamController, bir Stream'i "programatik olarak" (elle, istediğin zaman) besleyebileceğin bir araçtır. async*/yield ile otomatik akan bir stream'in aksine, burada sen ne zaman veri ekleneceğine karar veriyorsun — controller.add(...) çağırdığın her an, o değer stream'e "yayınlanır."

Not: dart:async kütüphanesini import etmemiz gerekti — StreamController, Dart'ın çekirdek (dart:core) kütüphanesinde değil, dart:async kütüphanesinde tanımlıdır. Bölüm 12'de kütüphaneler ve import'ları detaylı işleyeceğiz; şimdilik bazı ileri özelliklerin ayrı kütüphanelerden geldiğini bilmen yeterli.

StreamController'ın Parçaları

dart
StreamController<int> controller = StreamController<int>();

controller.stream  // dinlemek (listen) için kullanılan Stream nesnesi
controller.sink    // veri eklemek için kullanılan "giriş noktası" (add ile aynı işi görür)
controller.add(5)  // controller.sink.add(5) ile aynı etkiyi yapar, kısayoldur
controller.close() // stream'i kapatır, artık veri eklenemez

controller.stream, dışarıya (dinleyicilere) açılan taraf; controller.add() (veya controller.sink.add()), içeriye veri "besleyen" taraf. Bunu bir su borusu gibi düşünebilirsin — add() ile bir uçtan su akıtırsın, .stream.listen() ile diğer uçtan suyu toplarsın.

Neden StreamController Kullanılır? — Gerçek Dünya Senaryosu

async*/yield, önceden bilinen, otomatik bir akış için uygundur (örneğin "her saniye bir sayı üret"). Ama bazen, stream'e ne zaman veri ekleneceği dışarıdan tetiklenen bir olaya bağlıdır — örneğin bir buton her tıklandığında, ya da bir sensörden veri her geldiğinde:

dart
import 'dart:async';

class ButonTikayici {
  final StreamController<int> _controller = StreamController<int>();
  int _sayac = 0;

  Stream<int> get tiklamaStream => _controller.stream;

  void butonaTikla() {
    _sayac++;
    _controller.add(_sayac); // dışarıdan tetiklenen bir olayda veri ekleniyor
  }

  void kapat() {
    _controller.close();
  }
}

void main() {
  ButonTikayici buton = ButonTikayici();

  buton.tiklamaStream.listen((sayi) {
    print("Tıklama sayısı: $sayi");
  });

  buton.butonaTikla(); // Tıklama sayısı: 1
  buton.butonaTikla(); // Tıklama sayısı: 2
  buton.butonaTikla(); // Tıklama sayısı: 3

  buton.kapat();
}

Bu, Flutter'da çok yaygın bir kalıptır — bir buton her tıklandığında, bir text field her değiştiğinde, veya bir state değişikliği her olduğunda, StreamController ile bu olayları "yayınlayabilirsin", ve uygulamanın farklı yerleri bu stream'i dinleyerek tepki verebilir.

Tek Dinleyicili (Single-Subscription) vs Broadcast Stream

Varsayılan olarak, bir StreamController'ın stream'i sadece bir kere, bir dinleyici tarafından dinlenebilir:

dart
void main() {
  StreamController<int> controller = StreamController<int>();

  controller.stream.listen((veri) => print("Dinleyici 1: $veri"));
  controller.stream.listen((veri) => print("Dinleyici 2: $veri")); // ❌ HATA!

  controller.add(1);
}

Bu kod hata verir — Bad state: Stream has already been listened to. Varsayılan stream'ler, tek kullanımlık kabul edilir (buna "single-subscription stream" denir) — genelde bir dosya okuma gibi, "baştan sona bir kere işlenecek" senaryolar için tasarlanmıştır.

Broadcast Stream — Birden Fazla Dinleyici

Eğer birden fazla dinleyicinin aynı veriyi almasını istiyorsan, StreamController.broadcast() kullanman gerekir:

dart
void main() {
  StreamController<int> controller = StreamController<int>.broadcast();

  controller.stream.listen((veri) => print("Dinleyici 1: $veri"));
  controller.stream.listen((veri) => print("Dinleyici 2: $veri")); // ✅ artık çalışır!

  controller.add(1);
  controller.add(2);
}

Çıktı:

metin
Dinleyici 1: 1
Dinleyici 2: 1
Dinleyici 1: 2
Dinleyici 2: 2

broadcast(), tıpkı bir radyo yayını gibi çalışır — birden fazla "dinleyici" (subscriber) aynı anda aynı veriyi alabilir. Bu, örneğin bir uygulamanın birden fazla farklı ekranının, aynı "kullanıcı oturum durumu" stream'ini aynı anda dinlemesi gerektiğinde kullanışlıdır.

Broadcast Stream'in Önemli Bir Farkı — Geç Katılan Dinleyiciler

dart
void main() {
  StreamController<int> controller = StreamController<int>.broadcast();

  controller.add(1); // henüz kimse dinlemiyor, bu veri KAYBOLUR

  controller.stream.listen((veri) => print("Geç gelen dinleyici: $veri"));

  controller.add(2); // bu veri alınır
}

Çıktı:

metin
Geç gelen dinleyici: 2

Broadcast stream'lerde, bir dinleyici abone olmadan önce yayınlanan veriler, o dinleyici için kaybolur — tıpkı canlı bir radyo yayınına, yayın başladıktan sonra katılmak gibi, geçmiş yayını dinleyemezsin. Bu, single-subscription stream'lerden farklı bir davranıştır ve dikkat edilmesi gereken bir noktadır.

close() — Kaynak Yönetimi

dart
void main() {
  StreamController<int> controller = StreamController<int>();

  controller.stream.listen((veri) => print(veri));
  controller.add(1);

  controller.close(); // artık add() çağrılamaz, kaynaklar serbest bırakılır

  controller.add(2); // ❌ HATA! kapatılmış bir controller'a veri eklenemez
}

Bir önceki derste sonsuz stream'ler için verdiğimiz "kaynak sızıntısı" uyarısı burada somutlaşıyor: bir StreamController artık kullanılmayacaksa, .close() çağrılarak kaynakların serbest bırakılması önemlidir. Flutter'da, bu genelde bir widget'ın dispose() metodunda yapılır (Flutter tutorial'ında bunu tam olarak göreceğiz) — bir widget ekrandan kaldırıldığında, kullandığı StreamController'ları kapatmak, bellek sızıntılarını önler.


🎯 Bu Dersten Çıkarılması Gerekenler

  • StreamController, bir stream'e elle, istediğin an veri eklemeni sağlar (add() ile); dışarıdan tetiklenen olaylar için async*/yield'e göre daha uygundur.
  • Varsayılan StreamController, tek bir dinleyiciye izin verir (single-subscription); birden fazla dinleyici için StreamController.broadcast() kullanılmalıdır.
  • Broadcast stream'lerde, bir dinleyici abone olmadan önce yayınlanan veriler o dinleyici için kaybolur.
  • .close(), bir controller'ın kaynaklarını serbest bırakır — artık kullanılmayacak controller'lar için önemlidir (Flutter'da genelde dispose() içinde yapılır).

📝 Ödevler

  • [ ] Bir StreamController<int> oluştur, elle birkaç değer ekle (.add()), .listen() ile dinle.
  • [ ] Aynı controller'ı iki farklı dinleyici ile dinlemeyi dene (broadcast olmadan), hatayı gözlemle; sonra .broadcast() ile düzelt.
  • [ ] Broadcast bir stream'de, bir dinleyicinin geç abone olduğunda önceki verileri kaçırdığını kanıtlayan bir örnek yaz.
  • [ ] ButonTikayici benzeri bir sınıf yaz — dışarıdan tetiklenen bir olayı (örn. bir mesaj gönderme fonksiyonu) StreamController ile stream'e dönüştür.
  • [ ] Bir controller'ı .close() ile kapat, sonra .add() çağırmayı dene, hatayı gözlemle ve neden close()'un önemli olduğunu yorumla.

🏁 Bölüm 10 Tamamlandı!

Asenkron programlamanın tüm temel araçlarını işledik: Future, async/await, .then()/.catchError(), Stream, async*/yield ve StreamController. Bu, Flutter'da API çağrıları yapmak, gerçek zamanlı veri dinlemek ve kullanıcı etkileşimlerini yönetmek için doğrudan kullanacağın bilgi birikimi.

Sıradaki durak Bölüm 11 — Koleksiyonlarda İleri Fonksiyonel Programlama: map, where, reduce, fold gibi metodlarla, koleksiyonları çok daha güçlü ve kısa bir şekilde işlemeyi öğreneceğiz.