StreamBuilder: что это, принцип работы и применение во Flutter

Автор: IT Sectr Опубликовано: 2026-07-03 Время чтения: 8 мин

StreamBuilder — виджет Flutter, который автоматически перестраивает интерфейс при получении новых данных из асинхронного потока. В отличие от FutureBuilder, работающего с однократным результатом, StreamBuilder поддерживает непрерывное обновление UI на протяжении всего жизненного цикла Stream. По данным официальной документации Flutter (2026), StreamBuilder используется в приложениях реального времени: чаты, ленты новостей, мониторинг датчиков и финансовые тикеры. Это ключевой инструмент реактивного программирования, где UI отражает состояние данных без ручных вызовов setState.

Главное

  • StreamBuilder — виджет, принимающий Stream и snapshot данных для реактивного рендеринга UI
  • Snapshot содержит connectionState, data и error, определяющие текущее состояние потока
  • ConnectionState проходит четыре фазы: none, waiting, active, done
  • AsyncSnapshot — неизменяемый объект, который гарантирует консистентность данных на каждом кадре
  • StreamController управляет потоком: добавляет данные, обрабатывает ошибки и закрывает Stream

Что такое StreamBuilder

StreamBuilder — это виджет из пакета Flutter SDK, который подписывается на Stream и перестраивает свой дочерний элемент при каждом новом событии потока. StreamBuilder принимает объект Stream и возвращает widget на основе последнего snapshot, полученного из потока.

В архитектуре 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

StreamBuilder подписывается на Stream в момент сборки и отписывается при уничтожении виджета. Каждый раз, когда Stream испускает событие, StreamBuilder получает новый AsyncSnapshot и вызывает builder-функцию для перестроения UI.

Процесс состоит из трёх этапов. Первый: StreamBuilder создаёт подписку на переданный Stream через метод stream.listen. Второй: при каждом событии StreamBuilder обновляет внутренний AsyncSnapshot и помечает виджет как "грязный" для перестроения. Третий: фреймворк вызывает builder-функцию с новым snapshot, и UI отображает актуальные данные.

Важно: StreamBuilder использует StreamSubscription внутри себя. Если Stream передан напрямую, StreamBuilder подписывается один раз при инициализации. Если Stream меняется (например, при rebuild родителя), StreamBuilder отписывается от старого потока и подписывается на новый. Это поведение контролируется параметрами initialData и buildWhen, которые позволяют оптимизировать количество перестроений.

Вывод: понимание жизненного цикла подписки — основа правильного использования StreamBuilder. Неправильное управление стримами приводит к утечкам памяти или устаревшим данным в UI.

ConnectionState: четыре состояния потока

Свойство connectionState объекта AsyncSnapshot определяет, на каком этапе работы с потоком находится StreamBuilder. Различают четыре состояния: none, waiting, active, done.

ConnectionState.none

None — начальное состояние, когда Stream ещё не начал передавать данные. В этом состоянии snapshot.connectionState равен ConnectionState.none, а snapshot.data равен null. Обычно в этом состоянии отображают заглушку или ожидание первого события. Если Stream не предоставляет начальных данных, StreamBuilder начинает с этого состояния.

ConnectionState.waiting

Waiting — состояние ожидания данных от асинхронного потока. Stream активен, но данные ещё не поступили. Это состояние возникает, например, при загрузке данных из сети или при открытии долгоживущего соединения. В этом состоянии принято показывать CircularProgressIndicator или скелетон-загрузку.

ConnectionState.active

Active — поток испускает данные, и UI отображает актуальную информацию. В этом состоянии snapshot.hasData равно true, а snapshot.data содержит последнее значение из потока. Если поток — Broadcast Stream, активное состояние может сосуществовать с ожиданием новых данных.

ConnectionState.done

Done — поток завершён, новых данных не будет. Snapshot.data содержит последнее значение, переданное до закрытия стрима. Если поток успешно завершён, snapshot.hasError равен false. Это состояние используют для отображения финального результата: сообщение "Загрузка завершена" или переход к следующему экрану.

Вывод: при построении UI через StreamBuilder необходимо обрабатывать все четыре состояния, чтобы интерфейс корректно отображал загрузку, данные, ошибки и завершение.

Использование StreamController для управления потоком

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 потоков — стандартный контроллер, для shared-состояния — broadcast контроллер или BehaviourSubject.

Примеры кода с StreamBuilder

Пример 1 демонстрирует таймер с обратным отсчётом с использованием StreamController и 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();
      }
    });
  }
}

В примере создаётся контроллер для генерации чисел от 0 до 10 с интервалом 1 секунда. После достижения 10 вызывается close, и поток завершается. StreamBuilder, подписанный на stream этого контроллера, будет отображать каждое новое значение.

Пример 2 — использование StreamBuilder с Broadcast Stream для отображения данных из нескольких источников.

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}');
  },
)

Второй пример показывает обработку всех состояний: initialData для начального отображения, waiting для индикатора загрузки, hasError для ошибок и data для успешного результата. Такой паттерн — стандарт для production-кода с StreamBuilder.

Вывод: используйте initialData для избежания пустого экрана в первый момент и всегда обрабатывайте hasError для корректного отображения ошибок пользователю.

Типовые ошибки при работе с StreamBuilder

Ошибка 1: создание нового Stream при каждом rebuild родителя. Если 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.

Часто задаваемые вопросы

Чем StreamBuilder отличается от FutureBuilder?

FutureBuilder предназначен для однократного асинхронного результата: он подписывается на Future, получает одно значение и завершает работу. StreamBuilder подписывается на Stream, который может испускать множество значений во времени, и перестраивает UI при каждом новом событии.

Что такое AsyncSnapshot в StreamBuilder?

AsyncSnapshot — неизменяемый объект, который содержит текущее состояние подписки (connectionState), последнее полученное значение (data) и объект ошибки (error), если поток испустил исключение.

Как обработать ошибку в StreamBuilder?

Ошибка обрабатывается через свойство snapshot.hasError и snapshot.error в builder-функции. Если поток испускает ошибку методом sink.addError, AsyncSnapshot получает error, и builder должен отобразить соответствующее сообщение или fallback UI.

Можно ли использовать один Stream в нескольких StreamBuilder?

Да, если Stream — broadcast (создан через StreamController.broadcast). Single-subscription Stream допускает только одного подписчика. Для разделения одного потока между несколькими виджетами используйте broadcast контроллер или пакет rxdart с BehaviourSubject.

Как избежать перестроения StreamBuilder при каждом событии?

Используйте параметр buildWhen для фильтрации событий, при которых нужно перестраивать UI. Также применяйте Stream.transformer или Stream.where для фильтрации данных до передачи в StreamBuilder.

Итоги

  • StreamBuilder — виджет для реактивного построения UI по асинхронному потоку данных, поддерживающий непрерывное обновление интерфейса
  • AsyncSnapshot содержит connectionState (none, waiting, active, done), data и error — все состояния потока
  • StreamController управляет жизненным циклом потока: добавление данных, обработка ошибок и закрытие потока
  • Broadcast Stream позволяет нескольким StreamBuilder подписаться на один поток, single-subscription — только одному
  • Обработка ошибок обязательна: без проверки hasError приложение может зависнуть в состоянии загрузки
  • Утечка памяти — самая частая проблема: всегда закрывайте StreamController в dispose
  • Рекомендация: всегда указывайте initialData и обрабатывайте все четыре connectionState для бесшовного UX

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также