Что такое Stream в Dart и как он используется во Flutter?

«Что такое Stream в Dart и как он используется во Flutter?» — вопрос из категории Реактивное программирование, который задают на 59% собеседований Flutter Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Stream в Dart — это последовательность асинхронных событий. Это фундаментальная абстракция для реактивного программирования во Flutter, используемая для обработки данных, поступающих со временем: пользовательские события, ответы от сервера, показания датчиков, обновления состояния.

Базовый пример создания и подписки:

// Создание простого Stream с использованием async*
Stream<int> countDown(int from) async* {
  for (int i = from; i >= 0; i--) {
    await Future.delayed(const Duration(seconds: 1));
    yield i; // Каждую секунду выдаём новое значение
  }
}

void main() {
  final stream = countDown(5);
  // Подписка на поток
  final subscription = stream.listen((value) {
    print('Осталось: $value сек');
  }, onDone: () {
    print('Отсчёт завершён!');
  });
  // subscription.cancel(); // Для отмены подписки
}

Ключевые операции с Stream (трансформации):

stream
  .where((value) => value.isEven) // Фильтрация
  .map((value) => value * 2)      // Преобразование
  .take(3)                        // Ограничение количества событий
  .listen(print);

Использование во Flutter с StreamBuilder: StreamBuilder — это виджет, который автоматически перестраивает UI в ответ на новые данные из Stream.

class DataDisplayWidget extends StatelessWidget {
  final Stream<String> dataStream;

  const DataDisplayWidget({super.key, required this.dataStream});

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<String>(
      stream: dataStream,
      builder: (context, snapshot) {
        if (snapshot.hasError) {
          return Text('Ошибка: ${snapshot.error}');
        }
        switch (snapshot.connectionState) {
          case ConnectionState.waiting:
            return const CircularProgressIndicator();
          default:
            return Text('Полученные данные: ${snapshot.data}');
        }
      },
    );
  }
}

Важные концепции:

  • Холодный (Cold) Stream: Начинает генерировать события при появлении первого слушателя (как в примере с async*).
  • Горячий (Hot) Stream: Генерирует события независимо от наличия слушателей (например, StreamController.broadcast()).
  • Управление подпиской: Крайне важно отменять подписки (subscription.cancel()) в методе dispose() StatefulWidget или использовать пакеты вроде rxdart, которые предоставляют автоматическое управление жизненным циклом.

Streams лежат в основе многих популярных менеджеров состояний Flutter, таких как BLoC (где StreamController используется для приёма событий и эмиссии состояний).