[英]Flutter nested StreamBuilders causing Bad state: Stream has already been listened to
I'm trying to build a Flutter app using the BLoC pattern described in the video Flutter / AngularDart – Code sharing, better together (DartConf 2018) 我正在尝试使用视频Flutter / AngularDart中描述的BLoC模式构建一个Flutter应用程序- 代码共享,更好地在一起(DartConf 2018)
A BLoC is basically a view model with Sink
inputs and Stream
outputs. BLoC基本上是一个带有
Sink
输入和Stream
输出的视图模型。 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;
}
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
. 我在层次结构根目录附近的小部件中定义了BLoC,并将其传递给它下面的小部件,包括嵌套的
StreamBuilders
。 Like so: 像这样:
The top StreamBuilder
listens to a showLoading
stream on the BLoC so that it can rebuild to show an overlaid progress spinner. 顶级
StreamBuilder
侦听showLoading
流,以便它可以重建以显示重叠的进度微调器。 The bottom StreamBuilder
listens to a submitEnabled
stream to enable/disable a button. 底部
StreamBuilder
侦听submitEnabled
流以启用/禁用按钮。
The problem is whenever the showLoading
stream causes the top StreamBuilder
to rebuild the widget it rebuilds nested widgets too. 问题是当
showLoading
流导致顶级StreamBuilder
重建窗口小部件时,它也会重建嵌套窗口小部件。 This in itself is fine and expected. 这本身就很好并且预期。 However this results in the bottom
StreamBuilder
being recreated. 但是,这会导致重新创建底部的
StreamBuilder
。 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
当发生这种情况时,它会尝试重新订阅
submitEnabled
上的现有submitEnabled
流,从而导致Bad state: Stream has already been listened to
Is there any way to accomplish this without making all of the outputs BroadcastStreams
? 有没有办法在不创建所有输出
BroadcastStreams
情况下实现这一目标?
(There is also a chance that I'm fundamentally misunderstanding the BLoC pattern.) (我也有可能从根本上误解BLoC模式。)
Actual code example below: 下面的实际代码示例:
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();
}
}
As i understand BLoC you should only have one output stream which is connected to a StreamBuilder. 据我了解BLoC,您应该只有一个连接到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/github_search_widget.dart
New Link: https://github.com/ReactiveX/rxdart/blob/master/example/flutter/github_search/lib/search_widget.dart 新链接: https : //github.com/ReactiveX/rxdart/blob/master/example/flutter/github_search/lib/search_widget.dart
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. 如果需要组合多个流来生成模型(sowLoading和submitEnabled),则可以使用RxDart中的
Observable.combineLatest
将多个流合并为一个流。 I use this approach and it works really nice. 我使用这种方法,它的工作非常好。
使用BehaviorSubject而不是StreamController.BehaviorSubject将最近的事件发送给使用者
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.