StreamBuilder — Flutter виџет који аутоматски преграђује интерфејс при пријему нових података из асинхроног тока. За разлику од FutureBuilder-а који ради са једнократним резултатом, StreamBuilder подржава непрекидно ажурирање UI-ја током целог животног циклуса Stream-а. Према званичној документацији Flutter-а (2026), StreamBuilder се користи у апликацијама реалног времена: четови, вести, мониторинг сензора и финансијски тикери. Ово је кључни алат реактивног програмирања, где UI одражава стање података без ручних позива setState.
Главно
StreamBuilder — је виџет из пакета Flutter SDK који се претплаћује на Stream и преграђује свој потомак при сваком новом догађају тока. StreamBuilder прима Stream објекат и враћа виџет на основу последњег снимка добијеног из тока.
У архитектури Flutter-а, StreamBuilder припада групи Builder-виџета који одвајају изградњу UI-ја од стања података. За разлику од StatefulWidget-а, где промена стања захтева експлицитни позив setState, StreamBuilder реагује на асинхроне догађаје аутоматски, што поједностављује код и смањује ризик од грешака синхронизације.
За разлику од FutureBuilder-а, који обрађује једну асинхрону вредност, StreamBuilder је намењен за непрекидне токове података. FutureBuilder се завршава након пријема првог резултата, док StreamBuilder наставља да ослушкује ток и ажурира UI при сваком новом догађају.
StreamBuilder се користи у свим сценаријима где подаци пристижу непрекидно: WebSocket конекције, повратни позиви сензора, обавештења из Firebase-а, редови Bluetooth догађаја и емитовање стања апликације путем BLoC-а. Према анализи Flutter пројеката на GitHub-у (2025), StreamBuilder је међу три најчешће коришћена Builder-виџета, уз FutureBuilder и LayoutBuilder.
Закључак: користите StreamBuilder свуда где UI треба да одражава податке који се непрекидно мењају, избегавајући ручно управљање стањем путем StatefulWidget-а.
StreamBuilder се претплаћује на Stream у тренутку изградње и одјављује се при уништењу виџета. Сваки пут када Stream емитује догађај, StreamBuilder добија нови AsyncSnapshot и позива builder функцију за преграђивање UI-ја.
Процес се састоји од три корака. Први: StreamBuilder креира претплату на прослеђени Stream кроз метод stream.listen. Други: при сваком догађају StreamBuilder ажурира унутрашњи AsyncSnapshot и означава виџет као „прљав“ за преграђивање. Трећи: оквир позива builder функцију са новим снимком и UI приказује актуелне податке.
Важно: StreamBuilder користи StreamSubscription интерно. Ако је Stream прослеђен директно, StreamBuilder се претплаћује једном при иницијализацији. Ако се Stream промени (нпр. при поновној изградњи родитеља), StreamBuilder се одјављује са старог тока и претплаћује на нови. Ово понашање контролишу параметри initialData и buildWhen, који омогућавају оптимизацију броја преградњи.
Закључак: разумевање животног циклуса претплате је основа правилног коришћења StreamBuilder-а. Неправилно управљање токовима доводи до цурења меморије или застарелих података у UI-ју.
Својство connectionState објекта AsyncSnapshot одређује у којој фази рада са током се налази StreamBuilder. Разликују се четири стања: none, waiting, active, done.
None — почетно стање када Stream још није почео да преноси податке. У овом стању snapshot.connectionState је једнак ConnectionState.none, а snapshot.data је null. Обично се у овом стању приказује чувар места или чекање на први догађај. Ако Stream не обезбеђује почетне податке, StreamBuilder почиње из овог стања.
Waiting — стање чекања на податке из асинхроног тока. Stream је активан, али подаци још нису стигли. Ово стање настаје, на пример, при учитавању података из мреже или отварању дуготрајне везе. У овом стању се обично приказује CircularProgressIndicator или скелет учитавања.
Active — ток емитује податке, а UI приказује актуелне информације. У овом стању snapshot.hasData је true, а snapshot.data садржи последњу вредност из тока. Ако је Stream Broadcast Stream, активно стање може коегзистирати са чекањем нових података.
Done — ток је завршен, нових података неће бити. Snapshot.data садржи последњу вредност прослеђену пре затварања тока. Ако је ток успешно завршен, snapshot.hasError је false. Ово стање се користи за приказ коначног резултата: порука „Учитавање завршено“ или прелазак на следећи екран.
Закључак: при изградњи UI-ја путем StreamBuilder-а потребно је обрадити сва четири стања како би интерфејс исправно приказивао учитавање, податке, грешке и завршетак.
StreamController — је класа из пакета dart:async која креира и управља Stream-ом. StreamController омогућава додавање података, обраду грешака и затварање тока, контролишући његов животни циклус.
StreamController постоји у два типа: single-subscription (један претплатник) и broadcast (више претплатника). Single-subscription контролер прима само једног слушаоца истовремено — поновна претплата ће изазвати изузетак. Broadcast контролер омогућава више StreamBuilder-а да истовремено ослушкују један ток, што је корисно за BLoC и заједничко стање апликације.
При креирању StreamController-а кроз StreamController<T>.broadcast(), подаци додати пре прве претплате се не репродукују новом претплатнику. Ако је потребно добити последњу вредност при повезивању, користи се BehaviourSubject из пакета rxdart, који кешира последњи догађај.
Након завршетка рада са контролером потребно је позвати controller.close(). Непозивање close доводи до цурења ресурса: ток остаје отворен, претплатници и даље висе у меморији, а GC не ослобађа повезане објекте.
Закључак: користите StreamController са експлицитним управљањем животним циклусом. За single-subscription токове — стандардни контролер, за дељено стање — broadcast контролер или BehaviourSubject.
Пример 1 приказује тајмер са одбројавањем коришћењем StreamController-а и StreamBuilder-а.
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();
}
});
}
}
У примеру се креира контролер за генерисање бројева од 0 до 10 у интервалу од 1 секунде. Након достизања 10, позива се close и ток се завршава. StreamBuilder, претплаћен на stream овог контролера, приказиваће сваку нову вредност.
Пример 2 — коришћење StreamBuilder-а са Broadcast Stream-ом за приказ података из више извора.
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}');
},
)
Други пример приказује обраду свих стања: initialData за почетни приказ, waiting за индикатор учитавања, hasError за грешке и data за успешан резултат. Овај образац је стандард за производни код са StreamBuilder-ом.
Закључак: користите initialData да бисте избегли празан екран у првом тренутку и увек обрађујте hasError за исправно приказивање грешака кориснику.
Грешка 1: креирање новог Stream-а при свакој поновној изградњи родитеља. Ако се Stream прослеђује кроз израз који креира нови објекат при свакој изградњи, StreamBuilder се одјављује са старог и претплаћује на нови ток, изазивајући бесконачну петљу преградњи. Решење: користите remembered променљиву или StatefulWidget са фиксним Stream-ом.
Грешка 2: недостатак обраде грешака. Stream може емитовати грешке кроз controller.sink.addError, и ако builder не проверава snapshot.hasError, корисник види празан екран или бесконачно учитавање. Решење: увек проверавајте hasError и приказујте разумљиву поруку.
Грешка 3: цурење меморије због незатвореног StreamController-а. Ако контролер није затворен у dispose, ток наставља да постоји и GC не ослобађа меморију. Решење: позивајте controller.close() у dispose и ослушкујте done догађај за завршне радње.
Грешка 4: коришћење StreamBuilder-а са спором builder функцијом. Пошто се builder позива при сваком догађају тока, тешка израчунавања унутар њега доводе до прескакања оквира. Решење: преместите израчунавања у засебан изолат или користите Stream.map за трансформацију података.
Закључак: StreamBuilder је моћан, али захтеван алат. Пратите животни циклус Stream-а, обрађујте грешке и избегавајте тешке операције у builder-у.
Често постављана питања
FutureBuilder је намењен за једнократни асинхрони резултат: претплаћује се на Future, добија једну вредност и завршава рад. StreamBuilder се претплаћује на Stream, који може емитовати више вредности током времена, и преграђује UI при сваком новом догађају.
AsyncSnapshot — непроменљиви објекат који садржи тренутно стање претплате (connectionState), последњу примљену вредност (data) и објекат грешке (error), ако је ток емитовао изузетак.
Грешка се обрађује кроз својства snapshot.hasError и snapshot.error у builder функцији. Ако ток емитује грешку методом sink.addError, AsyncSnapshot добија error, и builder мора приказати одговарајућу поруку или резервни UI.
Да, ако је Stream broadcast (креиран кроз StreamController.broadcast). Single-subscription Stream дозвољава само једног претплатника. За дељење једног тока између више виџета користите broadcast контролер или пакет rxdart са BehaviourSubject-ом.
Користите параметар buildWhen за филтрирање догађаја при којима треба преградити UI. Такође примењујте Stream.transformer или Stream.where за филтрирање података пре прослеђивања StreamBuilder-у.
Резиме
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође