StreamBuilder: ce este, principiul de funcționare și aplicarea în Flutter

Autor: IT Sectr Publicat: 2026-07-03 Timp de citire: 8 min

StreamBuilder — un widget Flutter care reconstruiește automat interfața la primirea de noi date dintr-un flux asincron. Spre deosebire de FutureBuilder, care lucrează cu un rezultat unic, StreamBuilder suportă actualizarea continuă a UI pe întregul ciclu de viață al Stream. Conform documentației oficiale Flutter (2026), StreamBuilder este utilizat în aplicații în timp real: chat-uri, fluxuri de știri, monitorizarea senzorilor și tickere financiare. Este un instrument cheie al programării reactive, unde UI reflectă starea datelor fără apeluri manuale setState.

Principalele

  • StreamBuilder — widget care primește Stream și snapshot de date pentru randarea reactivă a UI
  • Snapshot conține connectionState, data și error, determinând starea curentă a fluxului
  • ConnectionState trece prin patru faze: none, waiting, active, done
  • AsyncSnapshot — un obiect imuabil care garantează consistența datelor la fiecare cadru
  • StreamController gestionează fluxul: adaugă date, procesează erori și închide Stream

Ce este StreamBuilder

StreamBuilder — este un widget din pachetul Flutter SDK care se abonează la Stream și reconstruiește elementul său copil la fiecare eveniment nou al fluxului. StreamBuilder primește un obiect Stream și returnează un widget pe baza ultimului snapshot obținut din flux.

În arhitectura Flutter, StreamBuilder aparține grupului de widget-uri Builder care separă construirea UI de starea datelor. Spre deosebire de StatefulWidget, unde schimbarea stării necesită apelul explicit setState, StreamBuilder reacționează la evenimente asincrone automat, simplificând codul și reducând riscul erorilor de sincronizare.

Spre deosebire de FutureBuilder, care procesează o singură valoare asincronă, StreamBuilder este destinat fluxurilor continue de date. FutureBuilder se încheie după primirea primului rezultat, în timp ce StreamBuilder continuă să asculte fluxul și să actualizeze UI la fiecare eveniment nou.

StreamBuilder este utilizat în toate scenariile unde datele sosesc continuu: conexiuni WebSocket, callback-uri ale senzorilor, notificări Firebase, cozi de evenimente Bluetooth și transmisii ale stării aplicației prin BLoC. Conform analizei proiectelor Flutter pe GitHub (2025), StreamBuilder se află în top trei cele mai utilizate widget-uri Builder, alături de FutureBuilder și LayoutBuilder.

Concluzie: utilizați StreamBuilder oriunde UI trebuie să reflecte date în continuă schimbare, evitând gestionarea manuală a stării prin StatefulWidget.

Cum funcționează StreamBuilder

StreamBuilder se abonează la Stream în momentul construirii și se dezabonează la distrugerea widget-ului. De fiecare dată când Stream emite un eveniment, StreamBuilder primește un nou AsyncSnapshot și apelează funcția builder pentru reconstruirea UI.

Procesul constă din trei etape. Prima: StreamBuilder creează o abonare la Stream prin metoda stream.listen. A doua: la fiecare eveniment, StreamBuilder actualizează AsyncSnapshot intern și marchează widget-ul ca „murdar“ pentru reconstruire. A treia: framework-ul apelează funcția builder cu noul snapshot, iar UI afișează datele actuale.

Important: StreamBuilder folosește StreamSubscription intern. Dacă Stream este transmis direct, StreamBuilder se abonează o dată la inițializare. Dacă Stream se schimbă (de exemplu, la reconstruirea părintelui), StreamBuilder se dezabonează de la fluxul vechi și se abonează la cel nou. Acest comportament este controlat de parametrii initialData și buildWhen, care permit optimizarea numărului de reconstruiri.

Concluzie: înțelegerea ciclului de viață al abonării este baza utilizării corecte a StreamBuilder. Gestionarea incorectă a fluxurilor duce la scurgeri de memorie sau date învechite în UI.

ConnectionState: patru stări ale fluxului

Proprietatea connectionState a obiectului AsyncSnapshot determină în ce etapă de lucru cu fluxul se află StreamBuilder. Se disting patru stări: none, waiting, active, done.

ConnectionState.none

None — starea inițială când Stream încă nu a început să transmită date. În această stare snapshot.connectionState este egal cu ConnectionState.none, iar snapshot.data este null. De obicei, în această stare se afișează un placeholder sau așteptarea primului eveniment. Dacă Stream nu furnizează date inițiale, StreamBuilder începe din această stare.

ConnectionState.waiting

Waiting — starea de așteptare a datelor din fluxul asincron. Stream este activ, dar datele încă nu au sosit. Această stare apare, de exemplu, la încărcarea datelor din rețea sau la deschiderea unei conexiuni de lungă durată. În această stare se afișează de obicei CircularProgressIndicator sau un schelet de încărcare.

ConnectionState.active

Active — fluxul emite date, iar UI afișează informația actuală. În această stare snapshot.hasData este true, iar snapshot.data conține ultima valoare din flux. Dacă Stream este un Broadcast Stream, starea activă poate coexista cu așteptarea de date noi.

ConnectionState.done

Done — fluxul este încheiat, nu vor mai fi date noi. Snapshot.data conține ultima valoare transmisă înainte de închiderea fluxului. Dacă fluxul s-a încheiat cu succes, snapshot.hasError este false. Această stare este utilizată pentru afișarea rezultatului final: mesajul „Încărcare finalizată“ sau trecerea la ecranul următor.

Concluzie: la construirea UI prin StreamBuilder, trebuie procesate toate cele patru stări pentru ca interfața să afișeze corect încărcarea, datele, erorile și finalizarea.

Utilizarea StreamController pentru gestionarea fluxului

StreamController — este o clasă din pachetul dart:async care creează și gestionează Stream. StreamController permite adăugarea de date, procesarea erorilor și închiderea fluxului, controlând ciclul său de viață.

StreamController există în două tipuri: single-subscription (un abonat) și broadcast (mai mulți abonați). Controlerul single-subscription acceptă doar un singur ascultător o dată — reabonarea va provoca o excepție. Controlerul broadcast permite mai multor StreamBuilder să asculte simultan același flux, ceea ce este util pentru BLoC și starea comună a aplicației.

La crearea StreamController prin StreamController<T>.broadcast(), datele adăugate înainte de prima abonare nu sunt redate noului abonat. Dacă trebuie să obțineți ultima valoare la conectare, utilizați BehaviourSubject din pachetul rxdart, care cachează ultimul eveniment.

După încheierea lucrului cu controlerul, trebuie apelat controller.close(). Neapelarea close duce la scurgeri de resurse: fluxul rămâne deschis, abonații persistă în memorie, iar GC nu eliberează obiectele asociate.

Concluzie: utilizați StreamController cu gestionare explicită a ciclului de viață. Pentru fluxuri single-subscription — controler standard, pentru stare partajată — controler broadcast sau BehaviourSubject.

Exemple de cod cu StreamBuilder

Exemplul 1 demonstrează un timer cu numărătoare inversă utilizând StreamController și StreamBuilder.

dart
import 'dart:async';

class TimerWidget extends StatefulWidget {
  const TimerWidget({super.key});

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

  void startTimer() {
    int count = 0;
    Timer.periodic(Duration(seconds: 1), (timer) {
      controller.sink.add(count++);
      if (count > 10) {
        controller.close();
        timer.cancel();
      }
    });
  }
}

În exemplu, se creează un controler pentru generarea numerelor de la 0 la 10 cu interval de 1 secundă. După atingerea lui 10, se apelează close, iar fluxul se încheie. StreamBuilder, abonat la stream-ul acestui controler, va afișa fiecare nouă valoare.

Exemplul 2 — utilizarea StreamBuilder cu Broadcast Stream pentru afișarea datelor din mai multe surse.

dart
final StreamController<String> broadcastController =
    StreamController<String>.broadcast();

StreamBuilder<String>(
  stream: broadcastController.stream,
  initialData: 'Waiting for data...',
  builder: (context, AsyncSnapshot<String> snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return const Center(
        child: CircularProgressIndicator(),
      );
    }
    if (snapshot.hasError) {
      return Text('Error: ${snapshot.error}');
    }
    return Text('Data: ${snapshot.data}');
  },
)

Al doilea exemplu arată procesarea tuturor stărilor: initialData pentru afișarea inițială, waiting pentru indicatorul de încărcare, hasError pentru erori și data pentru rezultatul reușit. Acest model este standardul pentru codul de producție cu StreamBuilder.

Concluzie: utilizați initialData pentru a evita ecranul gol în primul moment și procesați întotdeauna hasError pentru afișarea corectă a erorilor utilizatorului.

Erori tipice la lucrul cu StreamBuilder

Eroarea 1: crearea unui nou Stream la fiecare reconstruire a părintelui. Dacă Stream este transmis printr-o expresie care creează un nou obiect la fiecare construire, StreamBuilder se dezabonează de la fluxul vechi și se abonează la cel nou, provocând o buclă infinită de reconstruiri. Soluție: utilizați o variabilă remembered sau StatefulWidget cu un Stream fix.

Eroarea 2: lipsa procesării erorilor. Stream poate emite erori prin controller.sink.addError, iar dacă builder nu verifică snapshot.hasError, utilizatorul vede un ecran gol sau o încărcare infinită. Soluție: verificați întotdeauna hasError și afișați un mesaj clar.

Eroarea 3: scurgerea de memorie din cauza StreamController neînchis. Dacă controlerul nu este închis în dispose, fluxul continuă să existe, iar GC nu eliberează memoria. Soluție: apelați controller.close() în dispose și ascultați evenimentul done pentru acțiunile finale.

Eroarea 4: utilizarea StreamBuilder cu o funcție builder lentă. Deoarece builder este apelat la fiecare eveniment al fluxului, calculele grele în interiorul său duc la omiterea cadrelor. Soluție: mutați calculele într-un izolat separat sau utilizați Stream.map pentru transformarea datelor.

Concluzie: StreamBuilder este un instrument puternic, dar exigent. Urmăriți ciclul de viață al Stream, procesați erorile și evitați operațiile grele în builder.

Întrebări frecvente

Cu ce se deosebește StreamBuilder de FutureBuilder?

FutureBuilder este destinat unui rezultat asincron unic: se abonează la Future, primește o singură valoare și își încheie lucrul. StreamBuilder se abonează la Stream, care poate emite multiple valori în timp, și reconstruiește UI la fiecare eveniment nou.

Ce este AsyncSnapshot în StreamBuilder?

AsyncSnapshot — un obiect imuabil care conține starea curentă a abonării (connectionState), ultima valoare primită (data) și obiectul de eroare (error), dacă fluxul a emis o excepție.

Cum se procesează o eroare în StreamBuilder?

Eroarea se procesează prin proprietățile snapshot.hasError și snapshot.error în funcția builder. Dacă fluxul emite o eroare prin metoda sink.addError, AsyncSnapshot primește error, iar builder trebuie să afișeze un mesaj corespunzător sau un UI de rezervă.

Se poate folosi un singur Stream în mai multe StreamBuilder?

Da, dacă Stream este de tip broadcast (creat prin StreamController.broadcast). Stream-ul single-subscription acceptă doar un abonat. Pentru partajarea unui flux între mai multe widget-uri, utilizați un controler broadcast sau pachetul rxdart cu BehaviourSubject.

Cum se evită reconstruirea StreamBuilder la fiecare eveniment?

Utilizați parametrul buildWhen pentru filtrarea evenimentelor la care trebuie reconstruit UI. De asemenea, aplicați Stream.transformer sau Stream.where pentru filtrarea datelor înainte de transmiterea către StreamBuilder.

Rezumat

  • StreamBuilder — widget pentru construirea reactivă a UI pe baza unui flux de date asincron, suportând actualizarea continuă a interfeței
  • AsyncSnapshot conține connectionState (none, waiting, active, done), data și error — toate stările fluxului
  • StreamController gestionează ciclul de viață al fluxului: adăugarea datelor, procesarea erorilor și închiderea fluxului
  • Broadcast Stream permite mai multor StreamBuilder să se aboneze la un flux, single-subscription — doar unuia
  • Procesarea erorilor este obligatorie: fără verificarea hasError aplicația poate rămâne în stare de încărcare
  • Scurgerea de memorie — cea mai frecventă problemă: închideți întotdeauna StreamController în dispose
  • Recomandare: setați întotdeauna initialData și procesați toate cele patru connectionState pentru un UX fluent

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și