Flutter状态管理(2)——单Stream和广播Stream
- 2019 年 10 月 6 日
- 筆記
在Flutter状态管理(1)——InheritedWidget中介绍了状态管理以及如何使用InheritedWidget来实现全局状态的管理。这篇博客将介绍如何使用Stream来实现状态管理。
Stream是一种流,在dart中用于异步产生数据,分为两种类型:单订阅Stream和广播Stream。单订阅Stream只允许在该Stream的整个生命周期内使用单个监听器,即使第一个subscription被取消了,也无法在这个流上监听到第二次事件;而广播Stream允许任意个数的subscription,可以随时随地给它添加subcontractor,只要新的监听开始工作流,它就能收到新的事件。
单Stream
Flutter中的StreamBuilder组件封装了Stream,可以根据不同的状态创建不同的Widget。
下面以一个异步加载网络数据的例子来展示:
class SingleStreamPage extends StatelessWidget { StreamController<String> stream = StreamController(); @override Widget build(BuildContext context) { doNetWork(); return Scaffold( appBar: AppBar( title: Text('单Stream'), ), body: Center( child: StreamBuilder<String>( builder: (context, snapshot) { if (snapshot == null || !snapshot.hasData) { return CircularProgressIndicator(); } else { return Text(snapshot.data); } }, stream: stream.stream, ), )); } void doNetWork() { Future.delayed(Duration(seconds: 5), () { stream.add('hello world'); }); } }
这里创建一个StreamController,模拟了一个网络耗时操作,等待5s后,往流中添加一个数据,那么StreamBuilder函数将会收到数据,显示文本,而一开始没有收到数据,就会显示菊花。
这种单Stream可以在一个页面中控制状态,因为只能有一个订阅者,因此只能做局部状态的控制。
广播Stream
广播Stream,可以有多个订阅者,当发布一个事件后,存在的多个订阅者就都可以收到消息。
这边举个例子:PageA根据数字计算其平方,PageB根据数字计算其立方,PageC负责发送数据。页面间的关系是PageA–>PageB–>PageC。
接收数据的页面
class PageA extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('平方页面'), ), body: Container( child: Column( children: <Widget>[ RaisedButton( onPressed: () { Navigator.of(context) .push(MaterialPageRoute(builder: (context) { return PageB(); })); }, child: Text('To Page B'), ), StreamBuilder<int>( builder: (context, snapshot) { if (snapshot.hasData) { return Text('${snapshot.data * snapshot.data}'); } else { return Text('no data'); } }, stream: StateSubject().streamController.stream, ) ], ), ), ); } }
可以看到,接收和单Stream是一样的。
发射数据
class PageC extends StatelessWidget { int num = 1; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('发射数据页面'), ), body: Container( child: Column( children: <Widget>[ RaisedButton( onPressed: () { StateSubject().update(num++); }, child: Text('发射数据'), ), ], ), ), ); } }
点击将会发送一个数据。
广播Stream
class StateSubject { static final StateSubject _instance = StateSubject._(); factory StateSubject() => StateSubject._instance; StreamController<int> streamController; StateSubject._() { streamController = StreamController.broadcast(); } void update(int num) { streamController.sink.add(num); } }
可以看到与单Stream的区别是使用了broadcast()构造方法创建的就会是广播Stream。
使用Stream进行全局状态的管理,有很大的局限性。因为这依赖于监听者的存在,而如果这个监听的页面还没出现或不在内存中,那么该页面的数据从哪里来呢?因为Stream是一旦消耗就没有了,因此如果那些还未出现的页面想消费一个已发送的事件,那只能是找某种方式将事件保存下来。这又会很麻烦,看来Stream的方式并不适合用在状态管理。
参考
- Using StreamBuilder in Flutter
- Flutter中的状态管理