StreamController în Dart

Creează un stream pe care îl alimentezi tu: ce pui cu add ajunge la cei care ascultă controller.stream.

Sintaxă
import 'dart:async';

StreamController<T>({void onListen()?, void onPause()?, void onResume()?, FutureOr<void> onCancel()?, bool sync = false})
Stream<T> get stream
StreamSink<T> get sink

Ce face StreamController

Un StreamController are două fețe. Prin stream dai altora un Stream pe care îl pot asculta. Prin add, addError și close (sau prin sink, care are aceleași metode) trimiți evenimentele.

Controller-ul obișnuit acceptă un singur ascultător și păstrează evenimentele până când acesta apare. Funcțiile onListen, onPause, onResume și onCancel îți spun ce face ascultătorul, ca să pornești sau să oprești producția de date.

Obiceiul bun este să ții controller-ul privat într-o clasă și să expui doar stream, ca restul codului să poată asculta, dar nu și trimite evenimente.

Parametri

Parametru Descriere
onListen Opțional: chemat când stream-ul primește un ascultător.
onPause / onResume Opțional: chemate când ascultătorul pune pauză sau reia.
onCancel Opțional: chemat când ascultătorul anulează abonamentul.
sync Opțional: dacă e true, evenimentele ajung imediat la ascultător (folosit rar).

Ce returnează

Un controller nou; stream dă stream-ul lui, sink o vedere doar pentru trimitere.

Exemple

Exemplul 1: Un coș de cumpărături care anunță schimbările

import 'dart:async';

class Cos {
  final _controller = StreamController<int>();
  final _produse = <String>[];

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

  void adauga(String produs) {
    _produse.add(produs);
    _controller.add(_produse.length);
  }

  Future<void> inchide() => _controller.close();
}

Future<void> main() async {
  final cos = Cos();
  final afisare = cos.numarProduse.forEach((n) => print('Coș: $n produse'));
  cos.adauga('lapte');
  cos.adauga('pâine');
  cos.adauga('ouă');
  await cos.inchide();
  await afisare;
}

Rezultat

Coș: 1 produse
Coș: 2 produse
Coș: 3 produse

Exemplul 2: stream și sink, plus onListen și onCancel

Valorile scrise înainte de ascultare sunt păstrate. first anulează abonamentul imediat ce primește prima valoare, de aceea mesajul din onCancel apare înaintea afișării.

import 'dart:async';

Future<void> main() async {
  final c = StreamController<String>(
    onListen: () => print('[a apărut un ascultător]'),
    onCancel: () => print('[ascultătorul a plecat]'),
  );
  final StreamSink<String> sink = c.sink;
  sink.add('Bună dimineața, Iași!');
  sink.add('Azi: 23 °C');

  final primul = await c.stream.first;
  print('primit: $primul');
}

Rezultat

[a apărut un ascultător]
[ascultătorul a plecat]
primit: Bună dimineața, Iași!

Greșeli frecvente

  • Uiți să chemi close(). Ascultătorii care așteaptă finalul (await for, toList) nu se mai termină.
  • Expui tot controller-ul în loc de controller.stream. Oricine poate trimite atunci evenimente sau închide stream-ul.

Comenzi înrudite

  • StreamController.add: add trimite o valoare în stream, addError trimite o eroare, iar close trimite semnalul de final.
  • StreamController.broadcast: Controller al cărui stream poate avea oricâți ascultători; fiecare primește evenimentele trimise cât timp ascultă.
  • StreamController.hasListener: hasListener spune dacă stream-ul controller-ului este ascultat acum; isClosed dacă s-a chemat deja close.
  • Stream: Un șir de valori care sosesc în timp, una după alta (mesaje, clicuri, linii dintr-un fișier); îl parcurgi cu await for.
  • StreamSink: Interfața „capătului de scriere” al unui stream: add, addError, addStream, close și done.