Stream
Stream
是 Dart
提供的一种数据流订阅管理的"工具",Stream
可以接收任何对象,接收的对象通过 StreamController
的 sink
进行添加,然后通过 StreamController
发送给 Stream
,通过 listen
进行监听,listen
会返回一个 StreamSubscription
对象,StreamSubscription
可以操作对数据流的监听,例如 pause
,resume
,cancel
等
分类
Single-subscription Stream
单订阅 stream,整个生命周期只允许有一个监听,如果该监听 cancel 了,也不能再添加另一个监听,而且只有当有监听了,才会发送数据,主要用于文件 IO
流的读取等。
StreamController _controller = StreamController();
Broadcast Stream
广播 stream,允许有多个监听,当添加了监听后,如果流中有数据存在就可以监听到数据,这种类型,不管是否有监听,只要有数据就会发送,用于需要多个监听的情况。
StreamController _controller = StreamController.broadcast();
简单使用:
// 创建单订阅类型 `StreamController`
StreamController _controller = StreamController();
// 创建 广播订阅类型 `StreamController`
//StreamController _controller = StreamController.broadcast();
late Sink _sink;
late StreamSubscription _subscription;
@override
void initState() {
super.initState();
// _sink 用于添加数据
_sink = _controller.sink;
// 添加数据,stream 会通过 `listen` 方法打印
_sink.add('A');
// _controller.stream 会返回一个单订阅 stream,
// 通过 listen 返回 StreamSubscription,用于操作流的监听操作
_subscription = _controller.stream.listen((data) => print('Listener: $data'));
_sink.add('B');
//调用 pause 方法后,stream 被堵住了,数据不继续发送了
_subscription.pause();
_sink.add('C');
//调用 resume 方法,重新开始发送数据
_subscription.resume();
_sink.add('D');
}
大家自行验证分别使用单订阅 / 广播类型的 StreamController 的输出。
StreamBuilder
前面提到了 stream 通过 listen
进行监听数据的变化,Flutter
就为我们提供了这么个部件 StreamBuilder
专门用于监听 stream 的变化,然后自动刷新重建。接着来看下源码
class StreamBuilder<T> extends StreamBuilderBase<T, AsyncSnapshot<T>> {
const StreamBuilder({
Key? key,
this.initialData,
Stream<T>? stream,
required this.builder,
}) : assert(builder != null),
super(key: key, stream: stream);
final AsyncWidgetBuilder<T> builder;
final T? initialData;
@override
AsyncSnapshot<T> initial() => initialData == null
? AsyncSnapshot<T>.nothing()
: AsyncSnapshot<T>.withData(ConnectionState.none, initialData as T);
Widget build(BuildContext context, AsyncSnapshot<T> currentSummary) => builder(context, currentSummary);
...
}
StreamBuilder
必须传入一个 AsyncWidgetBuilder
参数,初始值 initialData
可为空, stream
用于监听数据变化,接着看下 父类StreamBuilderBaseState
的源码;
/// State for [StreamBuilderBase].
class _StreamBuilderBaseState<T, S> extends State<StreamBuilderBase<T, S>> {
StreamSubscription<T>? _subscription;
late S _summary;
@override
void initState() {
super.initState();
_summary = widget.initial();
_subscribe();
}
@override
void didUpdateWidget(StreamBuilderBase<T, S> oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.stream != widget.stream) {
if (_subscription != null) {
_unsubscribe();
_summary = widget.afterDisconnected(_summary);
}
_subscribe();
}
}
@override
Widget build(BuildContext context) => widget.build(context, _summary);
@override
void dispose() {
_unsubscribe();
super.dispose();
}
void _subscribe() {
if (widget.stream != null) {
_subscription = widget.stream!.listen((T data) {
setState(() {
_summary = widget.afterData(_summary, data);
});
}, onError: (Object error, StackTrace stackTrace) {
setState(() {
_summary = widget.afterError(_summary, error, stackTrace);
});
}, onDone: () {
setState(() {
_summary = widget.afterDone(_summary);
});
});
_summary = widget.afterConnected(_summary);
}
}
void _unsubscribe() {
if (_subscription != null) {
_subscription!.cancel();
_subscription = null;
}
}
}
通过上面的源码分析,StreamBuilder
也是通过 setState
方法进行刷新界面,但是我们在页面调用 setState
刷新的话,会把整个界面都进行重构,但是通过 StreamBuilder
的话,只刷新其 builder
;
示例
在了解完Stream & StreamBuilder之后,重构 flutter 计数器demo
import 'dart:async';
import 'package:flutter/material.dart';
class CounterPage extends StatefulWidget {
@override _CounterPageState createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int _counter = 0;
final StreamController<int> _streamController = StreamController<int>();
@override void dispose() {
_streamController.close();
super.dispose();
}
@override Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Stream version of the Counter App')
),
body: Center(
child: StreamBuilder<int>(
stream: _streamController.stream,
initialData: _counter,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
return Text('You hit me: ${snapshot.data} times');
},
),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
_streamController.sink.add(++_counter);
}));
}
}
RxDart
RxDart 继承了原生Dart Stream Api,并且对 Stream 做了进一步封装,RxDart 提供了三种 Subject,都是广播流。
PublishSubject
PublishSubject 仅向监听器发送在订阅之后添加的 Stream 的事件
final subject = PublishSubject<int>();
// observer1 will receive all data and done events
subject.stream.listen(observer1);
subject.add(1);
subject.add(2);
// observer2 will only receive 3 and done event
subject.stream.listen(observe2);
subject.add(3);
subject.close();
BehaviorSubject
BehaviorSubject 会将最后发送的事件发送给刚刚订阅的监听器
final subject = BehaviorSubject<int>();
subject.stream.listen(print); // prints 1,2,3
subject.add(1);
subject.add(2);
subject.add(3);
subject.stream.listen(print); // prints 3
subject.stream.listen(print); // prints 3
subject.stream.listen(print); // prints 3
ReplaySubject
当有数据添加了,但是还没有监听的时候,它会将数据存储下来,等到有监听了,ReplaySubject 会将Strean已经发出的所有事件再发送到新的监听器。
final subject = ReplaySubject<int>();
subject.add(1);
subject.add(2);
subject.add(3);
subject.stream.listen(print); // prints 1, 2, 3
subject.stream.listen(print); // prints 1, 2, 3
subject.stream.listen(print); // prints 1, 2, 3
以上三种 sbuject 均继承自 Subject,在add 方法中,除了向StreamController 中 add event,还有一个 onAdd 的空方法,交给具体的子类做实现; ReplaySubject 重写了 onAdd 方法,将event 保存进一个队列 Queue 中, 而 BehaviorSubject 重新 onAdd 方法,将event保存进 wrapper 变量中,而 PublishSubject 并没有重写 onAdd 方法;
@override
void add(T event) {
...
_add(event);
}
void _add(T event) {
onAdd(event);
_controller.add(event);
}
void onAdd(T event) {}
类图
BLoC
虽然利用StreamBuild 可以减少setState 的刷新页面,但是从上述demo中可以看出,逻辑处理都在page 中,对于复杂的页面将导致page浮肿不利于后期业务的维护,所以新的编程思想应运而生----业务逻辑组件 Business Logic Component
首先亮出BLoC的设计思路图:
概念
在深入研究 BLoC 架构的逻辑之前,让我们先了解一下它的主要概念。
- 事件和操作 是用户与 UI 交互时的输入:例如,滑动或滚动。
- 状态 是对这些操作的反应,它们根据用户与接口交互所发起的事件而变化。
- BLoC 是负责业务逻辑的组件。它将事件转换为状态,并且是接收信息并随之作出响应的处理元素。
- 流 是用户界面 (UI) 和 BLoC 对作出反应的异步数据流。
BLoC 架构的逻辑为:当用户通过与 UI 交互执行操作时,此操作的相关信息将发送至 BLoC 组件。然后,BLoC 组件处理和解释这些信息,并通过更改 UI 组件的状态做出响应。
示例
重新使用BLoC 重构计数器demo
BlocProvider(
child: CounterPage(),
bloc: CounterBloc()
);
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final CounterBloc _bloc = BlocProvider.of<CounterBloc>(context);
return Scaffold(
body: SafeArea(
child: Container(
child: StreamBuilder(
initialData: _bloc.count,
stream: _bloc.countStream,
builder: (_, snapshot) =>
Text(
'${snapshot.data}',
),
),
)),
floatingActionButton: FloatingActionButton(
onPressed: () => _bloc.add(),
child: Icon(Icons.add),
),
);
}
}
class CounterBloc extends BaseBloc {
int _count = 0;
int get count => _count;
StreamController<int> _countController = StreamController.broadcast();
Stream<int> get countStream => _countController.stream;
void add() {
_count += 1;
_countController.sink.add(_count);
}
@override
void dispose() {
_countController.close();
}
}