Irbisa · Шпаргалка

Потоки (Streams)

Streams, StreamController, StreamBuilder, широковещательные потоки

Middle-разработчик · 23 вопроса

  1. 01

    Чем Future отличается от Stream и когда что использовать?

    Future отдаёт один результат или одну ошибку, один раз; Stream отдаёт сколько угодно значений и ошибок со временем и потом может завершиться.

  2. 02

    Что такое Stream в Dart и чем single-subscription стрим отличается от broadcast?

    Stream<T> представляет собой асинхронную последовательность событий: данные, ошибки и финальное завершение (done).

    // Single-subscription: exactly one listenerfinal single = StreamController<int>();single.stream.listen((v) => print('A got $v'));// single.stream.listen(...); // Bad state: Stream has already been listened to // Broadcast: many listeners, no replay…
  3. 03

    Что напечатает этот код и что изменится, если убрать .broadcast()?

    import 'dart:async'; Future<void> main() async {  final controller = StreamController<String>.broadcast();  controller.add('hello');  controller.stream.listen(print);  controller.add('world');  await controller.close();}

    Напечатается только world: broadcast-контроллер без слушателей сразу выбрасывает hello.

  4. 04

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

    FutureBuilder ждёт один Future и перестраивается, когда тот завершится; StreamBuilder подписывается на Stream и перестраивается на каждом событии, пока стрим не закроется.

    class _ProfileState extends State<Profile> {  late final Future<User> _user = api.fetchUser(widget.id);     // once  late final Stream<int> _unread = chat.watchUnread(widget.id); // over time   @override  Widget build(BuildContext context) {…
  5. 05

    Что даёт StreamController и как с его помощью превратить колбэк-API (датчик, сокет) в Stream?

    StreamController объединяет sink, в который вы пушите события (add, addError, close), и stream, который вы отдаёте наружу.

    Stream<int> watch(ValueListenable<int> source) {  late final StreamController<int> controller;  void push() => controller.add(source.value);   controller = StreamController<int>.broadcast(    onListen: () => source.addListener(push),      // first listener: start…
  6. 06

    Что такое StreamSubscription и как управлять её жизненным циклом в виджете?

    StreamSubscription и есть хэндл, который возвращает listen: через него доставку ставят на паузу, возобновляют и отменяют.

    class _TickerState extends State<Ticker> {  StreamSubscription<int>? _sub;  int _last = 0;   @override  void initState() {…
  7. 07

    Когда использовать await for, а когда listen на стриме?

    await for обрабатывает события по одному внутри async-функции и не берёт следующее, пока не закончится тело цикла; listen регистрирует колбэк, сразу возвращает управление и даёт StreamSubscription, чтобы потом отменить подписку.

    // Sequential: the next order is not delivered until this one is savedFuture<void> saveAll(Stream<Order> orders) async {  await for (final order in orders) {    await db.insert(order);    if (order.isLast) break;   // cancels the subscription  }…
  8. 08

    Что напечатает этот код и в каком порядке?

    Stream<int> numbers() async* {  print('start');  yield 1;  yield 2;} void main() {  final stream = numbers();  print('created');  stream.listen(print);  print('listened');}

    Напечатается created, listened, start, 1, 2.

  9. 09

    Что делает за вас StreamBuilder и о чём говорят connectionState и snapshot?

    StreamBuilder сам подписывается на стрим, перестраивает builder на каждом событии и отменяет подписку, когда виджет уходит из дерева.

    class _PricesState extends State<Prices> {  late final Stream<Quote> _quotes = repo.watchQuotes(widget.symbol);   @override  Widget build(BuildContext context) {    return StreamBuilder<Quote>(…
  10. 10

    Два виджета слушают один и тот же стрим, и прилетает «Bad state: Stream has already been listened to». Что происходит?

    Стрим single-subscription: он допускает ровно одного слушателя за всю жизнь, и отмена подписки место не освобождает.

    // wrong: two listeners on a single-subscription streamfinal controller = StreamController<int>();controller.stream.listen(print);controller.stream.listen(print);   // Bad state: already listened to// right: broadcast when you own the controllerfinal broadcast = StreamController<int>.broadcast();…
  11. 11

    Как ошибки проходят через Stream и что происходит с ошибкой, которую никто не обработал?

    Ошибки передаются как события: addError или throw внутри тела async* отправляют их по тому же конвейеру, что и данные, в onError слушателя.

    // wrong: data handled, errors escape to the zone and the crash reporter_sub = repo.messages.listen((m) => setState(() => _messages.add(m))); // right: handle both channels_sub = repo.messages.listen(  (m) => setState(() => _messages.add(m)),…
  12. 12

    Найдите баг: прогресс-бар доходит до 100%, но сообщение «Upload complete» так и не появляется.

    Stream<double> upload(Uploader client, File file) {  final progress = StreamController<double>();  client      .send(file, onProgress: (sent, total) => progress.add(sent / total))      .catchError((Object e, StackTrace s) => progress.addError(e, s));  return progress.stream;} Future<void> finish(Uploader client, File file) async {  await for (final p in upload(client, file)) {    setProgress(p);  }  showDone();}

    StreamController никогда не закрывается, поэтому стрим так и не отправляет done: после 100% await for продолжает ждать, и showDone() не вызывается, как и всё, что идёт после await finish(...) (скрытие индикатора, блок finally).

  13. 13

    Что такое StreamTransformer и как написать свой оператор для стрима без rxdart?

    StreamTransformer<S, T> превращает один стрим в другой через stream.transform(...); utf8.decoder и LineSplitter тоже являются трансформерами.

    // Per event: trim lines and drop blank onesfinal trimNonEmpty = StreamTransformer<String, String>.fromHandlers(  handleData: (line, sink) {    final t = line.trim();    if (t.isNotEmpty) sink.add(t);  },…
  14. 14

    Как объединить несколько стримов: merge, zip, combineLatest?

    merge пропускает каждое событие каждого стрима по мере прихода; zip спаривает первые события всех стримов, потом вторые и так далее; combineLatest отдаёт последние значения всех стримов каждый раз, когда любой из них что-то выдаёт.

    import 'package:rxdart/rxdart.dart'; // merge: every event from every source, in arrival orderfinal allMessages = Rx.merge<Message>([channelA.messages, channelB.messages]); // zip: the i-th question with the i-th answer…
  15. 15

    Что добавляет пакет rxdart поверх dart:async: зачем нужны BehaviorSubject, debounceTime и switchMap?

    rxdart добавляет BehaviorSubject (стрим с текущим значением), debounceTime (ждать паузу) и switchMap (отменять предыдущий внутренний стрим); все они по-прежнему возвращают обычный Stream.

    import 'package:rxdart/rxdart.dart'; class SearchViewModel {  SearchViewModel(this._api);  final SearchApi _api;…
  16. 16

    Репозиторий на каждом опросе присылает идентичный список, и страница каждый раз перестраивается; вы добавили distinct(), но ничего не изменилось: почему?

    List (и любая модель без ==) сравнивается по ссылке, поэтому каждый свежераспарсенный результат опроса считается новым значением, и distinct() его пропускает.

  17. 17

    Вы пишете await feed.quotes.first, чтобы прочитать текущую цену, и вызов не возвращается никогда: что вы предположили о Stream такого, чего у него нет?

    Вы решили, что у стрима есть текущее значение.

  18. 18

    Снекбар (SnackBar) об успешном входе показывается три раза, потому что его вызывают внутри builder у StreamBuilder: какое правило нарушено?

    Правило такое: builder только рисует.

    // wrong: runs on every rebuild, not on every eventbuilder: (context, snap) {  if (snap.data is SignedIn) {    ScaffoldMessenger.of(context).showSnackBar(welcome);  }  return const HomeBody();…
  19. 19

    Найдите баг: после переключения между чатами в открытом чате продолжают появляться сообщения из чата, который вы покинули.

    class ChatController {  ChatController(this._repo);  final ChatRepository _repo;  final messages = ValueNotifier<List<Message>>(const []);  StreamSubscription<Message>? _sub;  void open(String chatId) {    messages.value = const [];    _sub = _repo        .watch(chatId)        .listen((m) => messages.value = [...messages.value, m]);  }  void dispose() {    _sub?.cancel();    messages.dispose();  }}

    open перезаписывает _sub, не отменяя предыдущую подписку, поэтому каждый открытый до этого чат продолжает слушать, и его сообщения попадают в тот же список.

  20. 20

    Как стримы используются для управления состоянием во Flutter и почему состояние редко держат в голом StreamController?

    Стримы описывают состояние как последовательность значений: источник выдаёт новое неизменяемое состояние, виджеты перестраиваются через StreamBuilder.

  21. 21

    Что такое backpressure и как с ним работают single-subscription и broadcast стримы?

    Backpressure возникает, когда медленный потребитель заставляет быстрый источник ждать.

    // A producer that respects backpressure: suspends at yield while pausedStream<List<int>> readChunks(File file) async* {  final raf = await file.open();  try {    while (true) {      final chunk = await raf.read(64 * 1024);…
  22. 22

    Лента котировок по WebSocket затихает на нестабильной мобильной сети, стрим не отдаёт ошибку, а UI бесконечно показывает старую цену: что добавить?

    Добавьте stream.timeout, чтобы тишина стала событием: если за окно не пришло ни одной цены, конвейер получит TimeoutException, а UI сможет показать «устарело» и переподключиться.

    class PriceFeed {  final _out = StreamController<Quote>.broadcast();  IOWebSocketChannel? _channel;  StreamSubscription<Quote>? _sub;  Timer? _retry;  int _attempt = 0;…
  23. 23

    Тест добавляет событие в StreamController и сразу проверяет слушателя, но тот ещё не отработал: решается ли это флагом sync: true?

    Нет. Асинхронная доставка входит в контракт: add планирует событие, а слушатель выполняется в следующей микрозадаче, поэтому код после add всегда завершается первым.

    // wrong: the assertion runs before the listener doestest('stores the value', () {  final controller = StreamController<int>();  var seen = 0;  controller.stream.listen((v) => seen = v);  controller.add(7);…