【问题标题】:StreamBuilder receives only last item from streamStreamBuilder 仅接收来自流的最后一项
【发布时间】:2019-05-23 00:57:03
【问题描述】:

我的 ApplicationBloc 是小部件树的根。在 bloc 的构造函数中,我正在侦听来自存储库的流,其中包含从 JSON 解码的模型,并将它们转发到 StreamBuilder 侦听的另一个流。

我希望 StreamBuilder 会一一接收模型并将它们添加到 AnimatedList 中。但是有一个问题:StreamBuilder 的构建器只在流中的最后一项触发一次。

例如,有几个模型位于本地存储中,id 为 0、1、2 和 3。所有这些都是从存储库发出的,所有这些都成功放入流控制器中,但只有最后一个模型(id == 3) 出现在 AnimatedList 中。

存储库:

class Repository {
  static Stream<Model> load() async* {
    //...
    for (var model in models) {
      yield Model.fromJson(model);
    }
  }
}

集团:

class ApplicationBloc {
  ReplaySubject<Model> _outModelsController = ReplaySubject<Model>();
  Stream<Model> get outModels => _outModelsController.stream;

  ApplicationBloc() {
    TimersRepository.load().listen((model) => _outModelsController.add(model));
  }
}

main.dart:

void main() {
  runApp(
    BlocProvider<ApplicationBloc>(
      bloc: ApplicationBloc(),
      child: MyApp(),
    ),
  );
}

//...

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    final ApplicationBloc appBloc = //...

    return MaterialApp(
      //...
      body: StreamBuilder(
        stream: appBloc.outModels,
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            var model = snapshot.data;
            /* inserting model to the AnimatedList */
          }

          return AnimatedList(/* ... */);
        },
      ),
    );
  }
}

有趣的通知:在 StreamBuilder 的 _subscribe() 方法中,onData() 回调触发了所需的次数,但 build() 方法只触发了一次。

【问题讨论】:

  • 如您所见,AsyncSnapshot&lt;T&gt; 具有data 属性,实际上是T data 而不是List&lt;T&gt; data- 所以它只保留一个&lt;T&gt; 元素而不是列表或数组或其他一些元素容器
  • @pskink 本来就是这个意思。 snapshot.data 仅包含一个元素,该元素将被添加到存储所有接收项目的列表控制器中。

标签: flutter rxdart


【解决方案1】:

您需要一个 Stream 来输出 List&lt;Model 而不是单个元素。此外,监听一个流以将其添加到另一个 ReplaySubject 将使输出流延迟 2 (!!!) 帧,因此最好有一个链。

class TimersRepository {
  // maybe use a Future if you only perform a single http request!
  static Stream<List<Model>> load() async* {
    //...
    yield models.map((json) => Model.fromJson(json)).toList();
  }
}

class ApplicationBloc {
  Stream<List<Model>> get outModels => _outModels;
  ValueConnectableObservable<List<Model>> _outModels;
  StreamSubscription _outModelsSubscription;

  ApplicationBloc() {
    // publishValue is similar to a BehaviorSubject, it always provides the latest value,
    // but without the extra delay of listening and adding to another subject
    _outModels = Observable(TimersRepository.load()).publishValue();

    // do no reload until the BLoC is disposed
    _outModelsSubscription = _outModels.connect();
  }

  void dispose() {
    // unsubcribe repo stream on dispose
    _outModelsSubscription.cancel();
  }
}

class _MyAppState extends State<MyApp> {
  ApplicationBloc _bloc;

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<List<Model>>(
      stream: _bloc.outModels,
      builder: (context, snapshot) {
        final models = snapshot.data ?? <Model>[];
        return ListView.builder(
          itemCount: models.length,
          itemBuilder: (context, index) => Item(model: models[index]),
        );
      },
    );
  }
}

【讨论】:

  • 感谢您的回复。第二个ReplaySubject 用于交付在运行时创建的新模型并为 StreamBuilder 提供无缝接口。是的,当然,使用 List 的技巧会起作用,尽管它有点不方便。更重要的是,我仍然不知道为什么我不能没有它。对我来说,这似乎是一条胶带。
  • 如果流将分别返回每个列表项 (Stream&lt;Model&gt;),StreamBuilder 将无法知道当前列表何时结束以及下一个(更新的)列表何时开始。
  • 想想一个列表项被删除的情况。您要向您的Subject 发送什么以表明该项目已被删除?您将不得不重新发送整个更新列表。
  • 拥有Stream&lt;List&lt;...&gt;&gt; 是您的做法,这不是黑客行为。我花了一段时间才学会这个。在其他平台上也是如此。流应该发出渲染 UI 所需的所有内容的快照。
  • 如果你担心性能:是的,会有很多不必要的映射、过滤和重新计算。这是故意的。它可以防止错误。而且 Dart 的处理速度足够快。
猜你喜欢
  • 1970-01-01
  • 2021-10-27
  • 2020-07-10
  • 2020-12-17
  • 1970-01-01
  • 2021-12-06
  • 2021-10-21
  • 2019-01-31
  • 1970-01-01
相关资源
最近更新 更多