Flutter嵌套的StreamBuilders导致Bad状态:Stream已被监听

Joe*_*ski 11 architecture dart flutter

我正在尝试使用视频Flutter/AngularDart中描述的BLoC模式构建一个Flutter应用程序- 代码共享,更好地在一起(DartConf 2018)

BLoC基本上是一个带Sink输入和Stream输出的视图模型.在我的例子中,它看起来有点像这样:

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

    // outputs
    Stream<bool> showLoading;
    Stream<bool> submitEnabled;
 }
Run Code Online (Sandbox Code Playgroud)

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

BLoC小部件层次结构

顶部StreamBuilder监听showLoadingBLoC上的流,以便它可以重建以显示重叠的进度微调器.底部StreamBuilder侦听submitEnabled流以启用/禁用按钮.

问题是当showLoading流导致top StreamBuilder重建窗口小部件时,它也会重建嵌套窗口小部件.这本身就很好并且预期.然而,这导致底部StreamBuilder被重新创建.当发生这种情况时,它会尝试重新订阅submitEnabledBLoC上的现有流Bad state: Stream has already been listened to

有没有办法在不进行所有输出的情况下实现这一目标BroadcastStreams

(我也有可能从根本上误解BLoC模式.)


下面的实际代码示例:

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();
  }
}
Run Code Online (Sandbox Code Playgroud)

Bau*_*kel 3

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

您可以在这里看到它是如何完成的: 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),您可以使用Observable.combineLatestRxDart 将多个流合并为一个流。我使用这种方法并且效果非常好。

  • 我确实看过那个示例,它很棒,但是它与视频中描述的模式不同,因为它只有一个包含整个状态模型的输出流。视频中的 BLoC 模式有多个可独立订阅的输出流,这就是我喜欢它的原因。否则我可能会使用像 redux 这样的东西 (8认同)