Flutter StreamBuilder создает снимки из предыдущего потока генератора

#flutter #dart #generator #stream-builder #dart-stream

#flutter #dart #поток #Генератор #stream-builder

Вопрос:

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

Я заметил очень странное поведение: когда выдается новый запрос и внутренний StreamBuilder перестраивается с новым потоком, он изначально получает данные из предыдущего потока. Это особенно заметно, когда новый поток пуст (например, запрос пользователя не дал результатов). Если новый поток пуст, он переходит от ConnectionState.waiting к ConnectionState.done , и оба события (я полагаю, ошибочно) заполняются данными из предыдущего потока генератора.

Вот некоторый код, который я написал, чтобы воспроизвести это как можно более изолированным способом. Для простоты я заменил внешний StreamBuilder на FutureBuilder, хотя поведение такое же.

 import 'dart:async';

import 'package:flutter/material.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  final Future<bool> oneSecondFuture = Future.delayed(Duration(seconds: 2), () {
    print('future completed!');
    return true;
  });

  @override
  Widget build(BuildContext context) {
    Stream<int> intStream(bool returnValues) async* {
      var lst = [1, 2, 3];
      var i = 0;
      while (returnValues) {
        if (i == lst.length) break;
        yield lst[i  ];
        await Future.delayed(Duration(milliseconds: 40));
      }
    }

    print('building');
    var futureBuilder = FutureBuilder(
      future: oneSecondFuture,
      builder: (_, boolSnap) {
        // If the future isn't complete yet, return a StreamBuilder with a nonempty stream
        if (!boolSnap.hasData) return StreamBuilder(
            stream: intStream(true),
            builder: (_, intSnap) {
              print('Nonempty steambuilder: '   intSnap.connectionState.toString()   '; '   intSnap.data.toString());
              return Text(intSnap.data.toString());
            }
        );

        // If the future is complete, return a StreamBuilder with an empty stream
        return StreamBuilder(
            stream: intStream(false),
            builder: (_, intSnap) {
              print('Empty steambuilder: '   intSnap.connectionState.toString()   '; '   intSnap.data.toString());
              return Text(intSnap.data.toString());
            }
        );
      }
    );

    return MaterialApp(
      title: 'Test StreamBuilder',
      home: futureBuilder,
    );
  }
}
  

Приведенный выше код выдает следующий вывод на печать:

 I/flutter (20225): building
I/flutter (20225): Nonempty streambuilder: ConnectionState.waiting; null
I/flutter (20225): Nonempty streambuilder: ConnectionState.active; 1
I/flutter (20225): Nonempty streambuilder: ConnectionState.active; 2
I/flutter (20225): Nonempty streambuilder: ConnectionState.active; 3
I/flutter (20225): Nonempty streambuilder: ConnectionState.done; 3
I/flutter (20225): building
I/flutter (20225): Nonempty streambuilder: ConnectionState.waiting; 3
I/flutter (20225): Nonempty streambuilder: ConnectionState.active; 1
I/flutter (20225): Nonempty streambuilder: ConnectionState.active; 2
I/flutter (20225): Nonempty streambuilder: ConnectionState.active; 3
I/flutter (20225): Nonempty streambuilder: ConnectionState.done; 3
I/flutter (20225): future completed!
I/flutter (20225): Empty streambuilder: ConnectionState.waiting; 3
I/flutter (20225): Empty streambuilder: ConnectionState.done; 3
  

Обратите внимание, что вторая сборка начинается со значения ‘3’ из последнего потока во время ожидания подключения. Пустой поток выдает значение 3 как для ожидающего, так и для выполненного события.

Если я создаю вторую идентичную функцию IntStream (т.Е. IntStream и intStreamTwo) и вызываю ее после завершения future, я получаю нулевые значения данных, как и ожидалось, поэтому кажется, что проблема связана с многократным вызовом одной функции генератора для получения разных потоков:

 I/flutter (20225): building
I/flutter (20225): Nonempty streambuilder: ConnectionState.waiting; null
I/flutter (20225): Nonempty streambuilder: ConnectionState.active; 1
I/flutter (20225): Nonempty streambuilder: ConnectionState.active; 2
I/flutter (20225): Nonempty streambuilder: ConnectionState.active; 3
I/flutter (20225): Nonempty streambuilder: ConnectionState.done; 3
I/flutter (20225): building
I/flutter (20225): Nonempty streambuilder: ConnectionState.waiting; 3
I/flutter (20225): Nonempty streambuilder: ConnectionState.active; 1
I/flutter (20225): Nonempty streambuilder: ConnectionState.active; 2
I/flutter (20225): Nonempty streambuilder: ConnectionState.active; 3
I/flutter (20225): Nonempty streambuilder: ConnectionState.done; 3
I/flutter (20225): future completed!
I/flutter (20225): using a different generator function:
I/flutter (20225): Empty streambuilder: ConnectionState.waiting; null
I/flutter (20225): Empty streambuilder: ConnectionState.done; null
  

Это предполагаемое поведение? Есть ли разумный способ заставить StreamBuilder не получать данные из предыдущего потока генератора? Я полагаю, я мог бы создать две идентичные функции генератора и чередовать их, но мне бы очень хотелось, чтобы здесь было менее сложное решение.

Ответ №1:

Я ответил на свой собственный вопрос, более внимательно прочитав документацию. Это кажется (невероятно запутанным) предполагаемым поведением. См. Документацию.

Однако мне все еще не ясно, почему использование нового потока из той же функции генератора вызовет такое поведение, тогда как использование нового потока из другой функции генератора — нет.

В любом случае, для всех, кто сталкивается с той же проблемой, моя предполагаемая работа — просто игнорировать ошибочные значения данных, когда ConnectionState «ожидает» или «готово» с некоторой дополнительной логикой всякий раз, когда поток пуст (например, снимок «готово» получен без получения действительного значения события).