Flutter嵌套的StreamBuilders导致错误状态:流已被侦听 [英] Flutter nested StreamBuilders causing Bad state: Stream has already been listened to

查看:789
本文介绍了Flutter嵌套的StreamBuilders导致错误状态:流已被侦听的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用视频 BLoC基本上是具有Sink输入和Stream输出的视图模型.在我的示例中,它看起来像这样:

A BLoC is basically a view model with Sink inputs and Stream outputs. In my example it looks a bit like this:

 class BLoC {
    // inputs
    Sink<String> inputTextChanges;
    Sink<Null> submitButtonClicks;

    // outputs
    Stream<bool> showLoading;
    Stream<bool> submitEnabled;
 }

我在层次结构根附近的小部件中定义了BLoC,并将其向下传递到其下的小部件,包括嵌套的StreamBuilders.像这样:

I have the BLoC defined in a widget near the root of the hierarchy and it is passed down to widgets beneath it, including nested StreamBuilders. Like so:

顶部的StreamBuilder在BLoC上侦听showLoading流,以便可以对其进行重建以显示重叠的进度条.底部的StreamBuilder侦听submitEnabled流以启用/禁用按钮.

The top StreamBuilder listens to a showLoading stream on the BLoC so that it can rebuild to show an overlaid progress spinner. The bottom StreamBuilder listens to a submitEnabled stream to enable/disable a button.

问题是,每当showLoading流导致顶部StreamBuilder重建小部件时,它也会同时重建嵌套的小部件.这本身是好的,也是可以预期的.但是,这将导致重新创建底部的StreamBuilder.发生这种情况时,它将尝试重新订阅BLoC上现有的submitEnabled流,从而导致Bad state: Stream has already been listened to

The problem is whenever the showLoading stream causes the top StreamBuilder to rebuild the widget it rebuilds nested widgets too. This in itself is fine and expected. However this results in the bottom StreamBuilder being recreated. When this happens it attempts to re-subscribe to the existing submitEnabled stream on the BLoC causing Bad state: Stream has already been listened to

有什么方法可以在不使所有输出为BroadcastStreams的情况下完成此操作吗?

Is there any way to accomplish this without making all of the outputs BroadcastStreams?

(从根本上来说,我还有可能误解了BLoC模式.)

(There is also a chance that I'm fundamentally misunderstanding the BLoC pattern.)

下面的实际代码示例:

import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart';
import 'dart:async';

void main() => runApp(BlocExampleApp());

class BlocExampleApp extends StatefulWidget {

  BlocExampleApp({Key key}) : super(key: key);

  @override
  _BlocExampleAppState createState() => _BlocExampleAppState();
}

class _BlocExampleAppState extends State<BlocExampleApp> {

  Bloc bloc = Bloc();

  @override
  Widget build(BuildContext context) =>
      MaterialApp(
        home: Scaffold(
            appBar: AppBar(elevation: 0.0),
            body: new StreamBuilder<bool>(
                stream: bloc.showLoading,
                builder: (context, snapshot) =>
                snapshot.data
                    ? _overlayLoadingWidget(_buildContent(context))
                    : _buildContent(context)
            )
        ),
      );

  Widget _buildContent(context) =>
      Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            TextField(
              onChanged: bloc.inputTextChanges.add,
            ),
            StreamBuilder<bool>(
                stream: bloc.submitEnabled,
                builder: ((context, snapshot) =>
                    MaterialButton(
                      onPressed: snapshot.data ? () => bloc.submitClicks.add(null) : null,
                      child: Text('Submit'),
                    )
                )
            )
          ]
      );

  Widget _overlayLoadingWidget(Widget content) =>
      Stack(
        children: <Widget>[
          content,
          Container(
            color: Colors.black54,
          ),
          Center(child: CircularProgressIndicator()),
        ],
      );
}

class Bloc {
  final StreamController<String> _inputTextChanges = StreamController<String>();
  final StreamController<Null> _submitClicks = StreamController();

  // Inputs
  Sink<String> get inputTextChanges => _inputTextChanges.sink;

  Sink<Null> get submitClicks => _submitClicks.sink;

  // Outputs
  Stream<bool> get submitEnabled =>
      Observable<String>(_inputTextChanges.stream)
          .distinct()
          .map(_isInputValid);

  Stream<bool> get showLoading => _submitClicks.stream.map((_) => true);

  bool _isInputValid(String input) => true;

  void dispose() {
    _inputTextChanges.close();
    _submitClicks.close();
  }
}

推荐答案

据我了解BLoC,您应该只有一个连接到StreamBuilder的输出流.此输出流发出一个包含所有必需状态的模型.

As i understand BLoC you should only have one output stream which is connected to a StreamBuilder. This output stream emits a model which contains all required state.

您可以在此处查看其操作方式:

You can see how its done here: https://github.com/ReactiveX/rxdart/blob/master/example/flutter/github_search/lib/github_search_widget.dart

新链接: https://github.com/ReactiveX/rxdart/blob/master/example/flutter/github_search/lib/search_widget.dart

如果需要组合多个流来生成模型(sowLoading和SubmitEnabled),则可以使用RxDart中的Observable.combineLatest将多个流合并为一个流.我使用这种方法,效果很好.

If you need to combine multiple steams to generate you model (sowLoading and submitEnabled), you can use Observable.combineLatest from RxDart to merge multiple streams into one stream. I use this approach and it works really nice.

这篇关于Flutter嵌套的StreamBuilders导致错误状态:流已被侦听的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆