我尝试使用视频Flutter / AngularDart – Code sharing, better together (DartConf 2018)中描述的BLoC模式构建Flutter应用
BLoC基本上是一个带有Sink
输入和Stream
输出的视图模型。在我的例子中,它看起来有点像这样:
class BLoC {
// inputs
Sink<String> inputTextChanges;
Sink<Null> submitButtonClicks;
// outputs
Stream<bool> showLoading;
Stream<bool> submitEnabled;
}
我在层次结构根目录附近的小部件中定义了BLoC,并将其传递给它下面的小部件,包括嵌套的StreamBuilders
。像这样:
顶部StreamBuilder
侦听BLoC上的showLoading
流,以便它可以重建以显示重叠的进度微调器。底部StreamBuilder
侦听submitEnabled
流以启用/禁用按钮。
问题是,只要showLoading
流导致顶层StreamBuilder
重建窗口小部件,它就会重建嵌套窗口小部件。这本身就很好并且预期。但是,这会导致重新创建底部StreamBuilder
。发生这种情况时,它会尝试重新订阅BLoC上的现有submitEnabled
流,从而导致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();
}
}
答案 0 :(得分: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),则可以使用RxDart中的Observable.combineLatest
将多个流合并为一个流。我使用这种方法,效果非常好。
答案 1 :(得分:0)
使用BehaviorSubject代替StreamController.BehaviorSubject将最近的事件发送给消费者