Ответ
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 используется для приёма событий и эмиссии состояний).
Ответ 18+ 🔞
Давай разберемся с этими вашими стримами в Dart, а то народ путается, как будто впервые про асинхронность слышит. Представь себе конвейерную ленту, ёпта. Не простую, а такую, по которой данные плывут не сразу все, а по кусочку, с паузами. Вот это и есть Stream. Штука архиполезная, когда у тебя что-то приходит со временем: клики юзера, ответы от сервера, который думает, как ленивая жопа, или, например, показания с гироскопа телефона.
Смотри, как эту ленту самому запустить и слушать:
// Делаем свою "ленту", которая отсчитывает секунды
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
.where((value) => value.isEven) // Отфильтруем, оставим только чётные. Нахуй нечётные!
.map((value) => value * 2) // Каждое значение умножаем на два. Просто потому что можем.
.take(3) // Берём только первые три штуки, а остальное — да похуй.
.listen(print); // И смотрим, что в итоге вышло.
А теперь самое охуенное — как это влепить в интерфейс Flutter!
Для этого есть виджет StreamBuilder. Он, сука, умный: сам подписывается на твою ленту, слушает её и перерисовывает часть экрана, как только приезжает новый кусок данных. Вообще красота.
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) {
// snapshot — это как снимок текущего состояния ленты
if (snapshot.hasError) {
// Если всё накрылось медным тазом и приехала ошибка
return Text('Ошибка: ${snapshot.error}');
}
switch (snapshot.connectionState) {
case ConnectionState.waiting:
// Лента есть, но данных ещё нихуя не приехало. Показываем крутилку.
return const CircularProgressIndicator();
default:
// Всё, данные приехали! Показываем их.
return Text('Полученные данные: ${snapshot.data}');
}
},
);
}
}
И пара важных концепций, чтобы не облажаться:
- Холодный (Cold) Stream: Это как кассета. Пока не нажмёшь play (не добавишь
listen), музыка не играет. Данные начинают генерироваться только для первого слушателя. Как в нашем примере сasync*. - Горячий (Hot) Stream: Это как радиоэфир. Данные льются постоянно, независимо от того, слушает тебя кто-то или нет. Например,
StreamController.broadcast()— это оно. - Управление подпиской — это пиздец важно: Если ты в виджете подписался на ленту, ты ОБЯЗАН отписаться, когда виджет помирает. Иначе будет утечка памяти, а твое приложение начнёт жрать оперативку, как не в себя. Делается это через
subscription.cancel()в методеdispose(). А ещё есть умные пакеты вродеrxdart, которые могут делать это за тебя, что очень удобно, чувак.
Кстати, на этих самых стримах, блядь, построены целые менеджеры состояний. Тот же BLoC — это по сути один большой, хитрожопый StreamController, который принимает события, перемалывает их и выплёвывает новые состояния в виде стрима. Так что разобраться тут — это прям основа основ.