↑↓ seç · Enter aç · Esc kapat

Mutlu Tekin
Mutlu Tekin
← Yazılar

Bölüm 10 — Konu 47: `Stream` Temelleri, `async*`, `yield`

3 dk okuma #dart
Dizi · 46/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. Future ile Stream Arasındaki Temel Fark
  2. async* — Stream Üreten Fonksiyon
  3. yield — Bir Değer "Yayınlamak"
  4. .listen() — Stream'i "Dinlemek"
  5. Stream'i await for ile Dinleme — async/await Tarzı
  6. Gerçek Dünya Senaryoları — Stream Ne Zaman Kullanılır?
  7. Stream Hata Yönetimi — try/catch ile
  8. Sonlu vs Sonsuz Stream
  9. 🎯 Bu Dersten Çıkarılması Gerekenler
  10. 📝 Ödevler

Future ile Stream Arasındaki Temel Fark

Bir önceki derste Future'ın "gelecekte bir kere gelecek bir değeri" temsil ettiğini öğrenmiştik. Peki ya bir kaynaktan zamanla, birden fazla değer gelecekse? İşte bunun için Stream kullanılır.

Basit bir benzetme:

  • Future = bir paket kargo (bir kere gelir, elinde tutarsın)
  • Stream = bir haber bülteni aboneliği (zamanla, birden fazla haber gelir)
dart
Stream<int> sayiUret() async* {
  for (int i = 1; i <= 5; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i; // her saniyede bir değer "yayınla"
  }
}

void main() {
  sayiUret().listen((sayi) {
    print("Alınan sayı: $sayi");
  });
}

Çıktı (her biri 1 saniye arayla):

metin
Alınan sayı: 1
Alınan sayı: 2
Alınan sayı: 3
Alınan sayı: 4
Alınan sayı: 5

async* — Stream Üreten Fonksiyon

Bölüm 10'da async fonksiyonların Future döndürdüğünü öğrenmiştik. async* (yıldızlı versiyon), bunun yerine bir Stream döndüren bir fonksiyon tanımlar:

dart
Stream<int> sayac() async* {
  // fonksiyon gövdesi, zamanla birden fazla değer "yayınlayabilir"
}

Dönüş tipi her zaman Stream<T>'dir — tıpkı async'in her zaman Future<T> döndürmesi gibi.

yield — Bir Değer "Yayınlamak"

yield, bir Stream'e yeni bir değer eklemenin yoludur — return gibi fonksiyonu sonlandırmaz, sadece bir değeri "dışarı gönderir" ve fonksiyon çalışmaya devam eder:

dart
Stream<String> mesajUret() async* {
  yield "Birinci mesaj";
  await Future.delayed(Duration(seconds: 1));
  yield "İkinci mesaj";
  await Future.delayed(Duration(seconds: 1));
  yield "Üçüncü mesaj";
}

void main() {
  mesajUret().listen((mesaj) {
    print(mesaj);
  });
}

Bu, Bölüm 5'te öğrendiğimiz return'den temelde farklıdır: return, fonksiyonu bir kere sonlandırıp tek bir değer döndürür; yield, fonksiyonu sonlandırmadan art arda birden fazla değer gönderebilir.

.listen() — Stream'i "Dinlemek"

dart
void main() {
  Stream<int> stream = sayiUret();

  stream.listen(
    (veri) {
      print("Veri geldi: $veri");
    },
    onError: (hata) {
      print("Hata: $hata");
    },
    onDone: () {
      print("Stream tamamlandı!");
    },
  );
}

.listen(), bir Stream'e abone olmanın yoludur. Üç önemli callback (geri çağırma fonksiyonu) alabilir:

  • İlk parametre (zorunlu): her yeni veri geldiğinde çalışır.
  • onError: stream içinde bir hata oluşursa çalışır.
  • onDone: stream tamamen bittiğinde (artık yeni veri gelmeyeceğinde) çalışır.

Stream'i await for ile Dinleme — async/await Tarzı

.listen() yerine, async/await'e benzer, daha okunabilir bir sözdizimi de kullanılabilir:

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

void main() async {
  await for (var sayi in sayiUret()) {
    print("Sayı: $sayi");
  }
  print("Stream bitti");
}

await for, Bölüm 3'te öğrendiğimiz for-in döngüsünün asenkron versiyonudur — her yeni değer geldiğinde döngü gövdesi çalışır, stream bitene kadar bu devam eder. Bu, .listen()'e göre genelde daha okunabilir bulunur, özellikle stream'den gelen her değeri sırayla işlemen gerektiğinde.

Gerçek Dünya Senaryoları — Stream Ne Zaman Kullanılır?

Future, "bir kerelik" asenkron işlemler için uygundur (bir API isteği, bir dosya okuma). Stream ise zamanla tekrar tekrar gelen veriler için kullanılır:

  • Kullanıcının yazdığı her karakteri anlık dinlemek (arama kutusu)
  • Bir sensörden (konum, ivmeölçer) sürekli veri almak
  • Bir sohbet uygulamasında gelen her yeni mesajı dinlemek
  • Bir dosyanın, parça parça (chunk chunk) okunması
dart
// Kavramsal örnek — gerçek bir sohbet uygulamasının basitleştirilmiş hali
Stream<String> mesajlariDinle() async* {
  // Gerçek bir uygulamada bu, bir sunucudan gelen WebSocket verisi olurdu
  await Future.delayed(Duration(seconds: 1));
  yield "Ahmet: Merhaba!";
  await Future.delayed(Duration(seconds: 2));
  yield "Ayşe: Selam, nasılsın?";
}

void main() async {
  print("Sohbete katılındı, mesajlar dinleniyor...");
  await for (var mesaj in mesajlariDinle()) {
    print(mesaj);
  }
}

Stream Hata Yönetimi — try/catch ile

dart
Stream<int> riskliStream() async* {
  yield 1;
  yield 2;
  throw Exception("Stream içinde hata oluştu");
}

void main() async {
  try {
    await for (var deger in riskliStream()) {
      print(deger);
    }
  } catch (e) {
    print("Hata yakalandı: $e");
  }
}

Çıktı:

metin
1
2
Hata yakalandı: Exception: Stream içinde hata oluştu

Bölüm 8'de öğrendiğimiz try/catch, await for ile de tutarlı bir şekilde çalışır — stream içinde bir hata fırlatılırsa, await for döngüsü durur ve hata catch bloğuna düşer.

Sonlu vs Sonsuz Stream

dart
// Sonlu stream — belirli sayıda değer üretip biter
Stream<int> sonluStream() async* {
  yield 1;
  yield 2;
  yield 3;
} // fonksiyon bittiğinde stream de "tamamlanır" (onDone tetiklenir)

// Sonsuz stream — teorik olarak hiç bitmez (dikkatli kullanılmalı!)
Stream<int> sonsuzStream() async* {
  int sayac = 0;
  while (true) {
    await Future.delayed(Duration(seconds: 1));
    yield sayac++;
  }
}

Bölüm 3'te while (true) için verdiğimiz uyarı burada da geçerli — sonsuz bir stream oluşturursan, onu dinleyen tarafın bir noktada dinlemeyi durdurması (.cancel() ile, bir sonraki konuda değinebiliriz) veya uygulamanın yaşam döngüsüyle yönetilmesi gerekir, aksi halde kaynak sızıntısına (resource leak) yol açabilir.


🎯 Bu Dersten Çıkarılması Gerekenler

  • Stream, zamanla birden fazla değer üreten bir asenkron kaynağı temsil eder (Future'ın aksine, tek bir değer değil).
  • async*, bir Stream<T> döndüren fonksiyon tanımlar; yield, fonksiyonu sonlandırmadan bir değer "yayınlar".
  • .listen(), bir stream'e abone olmanın temel yoludur; onError ve onDone callback'leri alabilir.
  • await for, stream'i for-in'e benzer, okunabilir bir şekilde dinlemenin yoludur.
  • try/catch, stream içindeki hataları da senkron kod gibi yakalayabilir.

📝 Ödevler

  • [ ] async* ve yield kullanarak, 1'den 5'e kadar sayıları birer saniye arayla üreten bir Stream yaz, .listen() ile dinle.
  • [ ] Aynı Stream'i await for ile dinle, iki yaklaşımı karşılaştır.
  • [ ] .listen()'in onDone callback'ini kullanarak, stream tamamlandığında özel bir mesaj yazdır.
  • [ ] Bir stream içinde throw ile hata fırlat, try/catch (await for ile) kullanarak yakala.
  • [ ] Kendi senaryonu düşün (örn. bir sipariş takip sistemi, sırayla durum güncellemeleri yayınlayan) ve bunu bir Stream ile modelle.

Sıradaki konu: Bölüm 10 — Konu 48: StreamController, Broadcast Stream