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. Обикновено в това състояние се показва placeholder или изчакване на първото събитие. Ако 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 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също