Sparround

Stream əsasları

Stream<T> — zamanla gələn dəyərlər ardıcıllığıdır. Future bir dəfə tamamlanır, Stream isə sıfır, bir və ya sonsuz sayda data hadisəsi göndərir, sonra ya done ilə bitir, ya da error verir (error-dan sonra da davam edə bilər).

Müqayisə üçün ən sadə model: Future<T> ≈ bir dəyər, Stream<T>Iterable<T>-in asinxron variantı.

Stream-i istehlak etməyin iki yolu var:

  • stream.listen(onData, onError:, onDone:, cancelOnError:) — geri StreamSubscription alırsan və nəzarət səndədir (pause, resume, cancel).
  • await for (final x in stream) { ... }async funksiya daxilində dövr kimi. break və ya return avtomatik olaraq subscription-ı ləğv edir.

Stream passivdir: listen çağırılana qədər (single-subscription halda) heç bir iş başlamır.

XüsusiyyətSingle-subscription (default)Broadcast
Dinləyici sayıÖmrü boyu **bir dənə**; ikinci `listen` `StateError` atırİstənilən qədər, istənilən vaxt
`listen`-dən əvvəlki hadisələrBufer olunur — dinləyici hamısını alır**İtir** — gec qoşulan əvvəlkiləri görmür
Pause davranışıMənbə dayanır və ya buferləyir (backpressure işləyir)Mənbə dayanmır — hadisələr həmin dinləyici üçün buferlənir
Tipik mənbəFayl oxuma, HTTP cavabı, `async*` generatorİstifadəçi hadisələri, event bus, `Timer`-lər
Necə yaradılır`StreamController<T>()``StreamController<T>.broadcast()` və ya `stream.asBroadcastStream()`

Ləğvetmə öhdəliyi. listen çağırdığın hər yerdə sən bir öhdəlik götürürsən: subscription-ı cancel() etmək. Ləğv edilməyən subscription:

  • callback-i və onun tutduğu (capture etdiyi) bütün obyektləri canlı saxlayır — yaddaş sızması;
  • obyekt "ölmüş" olsa belə hadisə emal etməyə davam edir — dispose olunmuş state-ə yazmaq cəhdləri;
  • mənbəni açıq saxlayır (socket, timer, platform kanalı) — batareya və trafik xərci.

Qayda sadədir: `listen` harada çağırılırsa, `cancel` da orada planlanmalıdır. Uzun ömürlü obyektdə subscription-ı sahədə saxla və dispose()-da ləğv et. Flutter-də bu, StatefulWidget-in dispose() metodudur; await for istifadə edirsənsə ləğvetmə avtomatikdir.

StreamController üçün əlavə öhdəlik: close(). Bağlanmayan controller done göndərmir, ona görə await for dövrəsi heç vaxt bitmir və controller-i tutan obyekt sərbəst buraxılmır.

Stream real tətbiqlərdə harada görünür? Cavabı hazır saxla — müsahibədə "nümunə ver" sualı mütləq gəlir:

  • İstifadəçi girişi: axtarış sahəsinə yazılan mətn (debounce ilə), scroll mövqeyi, jest hadisələri.
  • Şəbəkə: WebSocket, Server-Sent Events, böyük HTTP cavabının parça-parça oxunması (response.stream).
  • Platform: geolokasiya yeniləmələri, sensor və akselerometr, batareya statusu, şəbəkə bağlantısının dəyişməsi (connectivity), bildirişlər.
  • Verilənlər bazası: Firestore snapshots(), Drift/Isar watch() — sorğu nəticəsi dəyişəndə yeni hadisə gəlir.
  • Daxili event bus: auth statusunun dəyişməsi, dil dəyişikliyi — bunlar tipik broadcast stream-lərdir.

Mühüm nüans: bu mənbələrin əksəriyyəti broadcast olmalıdır, çünki bir neçə yerdən dinlənilir. Repository-də Stream qaytarırsansa, single-subscription qaytarmaq istifadəçini gözlənilməz StateError-a apara bilər.

Müsahibə ipucu. Klassik sual: "single-subscription və broadcast stream fərqi nədir?" Güclü cavab yalnız "dinləyici sayı" ilə bitmir — üç fərqi de: (1) dinləyici sayı; (2) buferləmə — single-subscription listen-ə qədər hadisələri saxlayır, broadcast isə saxlamır, gec qoşulan itirir; (3) backpressure — single-subscription-da pause() mənbəyi dayandıra bilir, broadcast-da yox.

İkinci sual: "subscription-ı ləğv etməsən nə olar?" Cavab: yaddaş sızması + dispose olunmuş obyektə yazma cəhdi + mənbənin açıq qalması. Bir cümlə ilə yekunlaşdır: `listen` bir öhdəlikdir, `cancel` isə onun ödənişidir.

Ən çox edilən səhv: Stream-i "bir neçə dəyər qaytaran Future" kimi izah edib buferləmə və ləğvetmə fərqlərini buraxmaq.

📚 Mənbələr və sənədlər