Flutter
作为响应式开发的框架,状态管理是Flutter
中非常重要的一个部分,接下来就来看看Flutter
中都有哪些状态管理的方式。
State/InheritedWidget
State
和InheritedWidget
在Flutter
状态管理中扮演着重要的角色,不少的系统控件都采用了这种方式进行状态管理。
State
因为Widget
是不可变的,但是State
支持跨帧保存数据,所以Widget
可以实现跨帧的状态恢复/刷新。当我们调用setState((){});
方法的时候,State
内部会通过调用markNeedsLayout
方式,将对应的Widget
设置为_diry
(脏标记),从而在下一帧执行WidgetBinding.darwFrame
时调用performLayout
进行更新。
InheritedWidget
InheritedWidget
在Flutter
常用于数据的共享(从上至下),被InheritedWidget
包裹起来的child可以
通过BuildContext
来获取相对应的数据。
所以State
和InheritedWidget
组成了Flutter
中最基础的状态管理模式,通过State
保存数据和管理状态,通过InheritedWidget
来进行数据的共享,从而实现了跨页面的数据传递。
示例
class InheritedText extends InheritedWidget {
final String text;
InheritedText({this.text, Widget child})
: super(child: child);
@override
bool updateShouldNotify(covariant InheritedText oldWidget) {
return text != oldWidget.text;
}
static InheritedText of(BuildContext context) {
// 此方法已被标记位过期方法,建议使用下面的方法
// return context.inheritFromWidgetOfExactType(InheritedText);
return context.dependOnInheritedWidgetOfExactType<InheritedText>() ?? null;
}
}
class DemoPage extends StatefulWidget {
@override
_DemoPageState createState() => _DemoPageState();
}
class _DemoPageState extends State<DemoPage> {
String _text = "init";
@override
Widget build(BuildContext context) {
return InheritedText(
text: _text,
child: Scaffold(
appBar: AppBar(),
body: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Builder(builder: (context) {
return Text(
InheritedText.of(context)?.text ?? "null",
style: TextStyle(color: Colors.blue),
);
}),
NextPage(),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
_text = "Hello";
});
},
),
),
);
}
}
class NextPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(child: Text(InheritedText.of(context)?.text ?? ""));
}
}
Stream
Stream达标事件流或者管道,通过Stream可以快速的实现给予事件驱动的业务逻辑,界面通过订阅事件,并针对事件进行变换处理(非必须),最后可以实现界面跟着事件流/管道进行更新。如下图:
简单Demo
如何通过Stream
更新StatelessWidget
?
class StreamDemoPage extends StatelessWidget {
final StreamController<int> _controller;
StreamDemoPage({Key key}):
_controller = StreamController<int>(),
super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Stream"),),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text("StatelessWidget 利用 Stream 完成UI刷新"),
StreamBuilder(
initialData: 0,
stream: _controller.stream,
builder: (context, snapshot) {
return Center(child: Text("${snapshot.data}"),);
},
)
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// 导入math package
// import 'dart:math';
_controller.add(Random.secure().nextInt(1000));
},
child: Icon(Icons.refresh),
),
);
}
}
效果如下:
Stream的工作流程
Flutter
中的Stream
、StreamController
、StreamSink
、StreamSubscription
都是对外开放的接口抽象,内部实现都是私有类。那么他们具体是什么关系呢?又是怎么实现事件流的呢?查看下图
在Flutter
中的事件流用Stream
表示,为了能方便控制Stream
,官方提供了StreamController
作为管理接口;同时StreamController
对外提供了StreamSink
对象作为事件的输入口,可以通过sink
属性访问;又提供Stream
对象用于监听和变化事件流;最后通过Stream
订阅得到Subscription
,可以管理事件订阅。
总结一下就是:
-
StreamController
用于控制Stream
的过程,提供各种借口用于创建各种事件流 -
StreamSink
事件的输入口,主要提供了add
、addStream
等方法 -
Stream
事件源本身,一般用于监听事件流或者对事件流进行变换,如listen
、where
、map
等 -
StreamSubscription
事件订阅后的得到的对象,可以用于取消订阅、暂停等操作
作为事件的入口,当通过StreamSink.add
添加一个事件是,事件最后会回调到Stream.listen
中传入的onData
方法,从add
到onData
的这个过程,在Stream
的内部就是通过_zone.runUnaryGuarded
进行衔接的,而完成这个衔接的恰好就是StreamSubscription
。
1、Stream
在listen
时传入了onData
方法用于回调,这个回调方法最终会被传入StreamSubscription
对象里,之后通过zone.registerUnaryCallback
注册得到_onData
标识,这个_onData
标识属于当前Zone
内的全局标识,只要获取_onData
就可以通过Zone
直接回调数据到listen
中的onData
。
2、StreamSink
在添加事件的时候,会执行StreamSubscription
中的_sendData
方法(会根据同步还是异步分别调用add
/addPending
),然后通过_zone.runUnaryGuarded(_onData, data)
执行上一步得到的_onData
对象,触发listen
传入的onData
方法,返回数据给订阅者。
上面的流程是同步Stream
,那么他的异步流程是怎么样实现的呢?
前面的部分都是相同的,只是在_sendData
方法中略有不同。同步的调用add
,异步的则是调用addPending
Stream 同步、异步
Stream除了异步执行以外,还可以同步执行,通过设置sync字段来控制,内部就会通过同步/还是异步返回不同的具体实例对象。
Stream构造方法
// 普通构造方法
factory StreamController(
{void onListen()?,
void onPause()?,
void onResume()?,
FutureOr<void> onCancel()?,
bool sync = false}) {
return sync
? _SyncStreamController<T>(onListen, onPause, onResume, onCancel)
: _AsyncStreamController<T>(onListen, onPause, onResume, onCancel);
/// 广播的stream
factory StreamController.broadcast(
{void onListen()?, void onCancel()?, bool sync = false}) {
return sync
? _SyncBroadcastStreamController<T>(onListen, onCancel)
: _AsyncBroadcastStreamController<T>(onListen, onCancel);
}
可以看出会根据sync
返回不同的实例对象,根据构造方法和sync
不同,分别有四个实例对象。
SyncStreamController._sendData
void _sendData(T data) {
if (_isEmpty) return;
if (_hasOneListener) {
_state |= _BroadcastStreamController._STATE_FIRING;
_BroadcastSubscription<T> firstSubscription =
_firstSubscription as dynamic;
firstSubscription._add(data);
_state &= ~_BroadcastStreamController._STATE_FIRING;
if (_isEmpty) {
_callOnCancel();
}
return;
}
_forEachListener((_BufferingStreamSubscription<T> subscription) {
subscription._add(data);
});
}
ASyncStreamController._sendData
void _sendData(T data) {
for (var subscription = _firstSubscription;
subscription != null;
subscription = subscription._next) {
subscription._addPending(new _DelayedData<T>(data));
}
}
通过上面的两个sendData方法的分析,可以看出两者最大的区别就是一个是调用add
方法另外一个是addPending
方法。
其中addPending方法最终会调用到stream_impl.dart
的schedule方法中
void schedule(_EventDispatch<T> dispatch) {
if (isScheduled) return;
assert(!isEmpty);
if (_eventScheduled) {
assert(_state == _STATE_CANCELED);
_state = _STATE_SCHEDULED;
return;
}
scheduleMicrotask(() {
int oldState = _state;
_state = _STATE_UNSCHEDULED;
if (oldState == _STATE_CANCELED) return;
handleNext(dispatch);
});
_state = _STATE_SCHEDULED;
}
整理一下异步的Stream发送流程,如下图:
StreamController的种类
上已经了解到Stream分区异步/同步两种类型,他们最要的区别就是混入的接口不一致,同步的混入的是_SyncStreamControllerDispatch
、异步混入的是_AsyncStreamControllerDispatch
。
- 同步 _SyncStreamController
- 异步 _AsyncStreamController
- 同步广播 _SyncBroadcastStreamController
- 异步广播 _AsyncBroadcastStreamController
Stream 变换
Stream支持事件的变换处理,通过Stream变化可以让事件经过筛选和多次处理,从而达到最终效果。
一般操作符变换实现对象,都是继承了_ForwardingStream
,在它内部的_ForwardingStreamSubscription
中,会把上一个Stream
的listen
添加到新的_handleData
回调,之后再回调里面调用新的(变换之后的)Stream
的_handleData
,通过这样子的嵌套回调,让Stream
在多次变换之后一直往后执行。
StreamBuilder
StreamBuilder是基于Stream的封装,能够让开发者快速的根据Stream构建应用。比如上面的基于Stream让StatelessWidget实现刷新效果的Demo。
那么StreamBuilder的内部是怎么实现的呢?下面是关键代码片段
class _StreamBuilderBaseState<T, S> extends State<StreamBuilderBase<T, S>> {
StreamSubscription<T> _subscription;
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) {
setState(() {
_summary = widget.afterError(_summary, error);
});
}, onDone: () {
setState(() {
_summary = widget.afterDone(_summary);
});
});
_summary = widget.afterConnected(_summary);
}
}
/// 取消订阅
void _unsubscribe() {
if (_subscription != null) {
_subscription.cancel();
_subscription = null;
}
}
}
整理之后的流程图如下:
RxDart
其实从订阅和变换的角度就可以看出,Dart中的Stream已经有用了ReactiveX的设计思想,RxDart的出现就是为了能帮助那些了解过ReactiveX的框架的开发者,能够快速的根据之前编写习惯上手,其实RxDart底层也是基于Stream的一种封装。下图就是RxDart和Stream的对应关系
Dart | RxDart |
---|---|
StreamController | Subject |
Stream | Observable |
下面用一个PublishSubject的发送和监听做示例:
创建Subject
class PublishSubject<T> extends Subject<T> {
PublishSubject._(StreamController<T> controller, Stream<T> stream)
: super(controller, stream);
/// 工厂方法内部,可以很明显的看到这里就是创建了一个广播类型的StreamController
factory PublishSubject(
{void Function() onListen, void Function() onCancel, bool sync = false}) {
// ignore: close_sinks
final controller = StreamController<T>.broadcast(
onListen: onListen,
onCancel: onCancel,
sync: sync,
);
return PublishSubject<T>._(
controller,
controller.stream,
);
}
}
添加事件
因为PublishSubject
继承自Subject
,所以add
方法在Subject
之中:
@override
void add(T event) {
if (_isAddingStreamItems) {
throw StateError(
'You cannot add items while items are being added from addStream');
}
_add(event);
}
/// 可以看到这里就是调用了controller.add方法
void _add(T event) {
onAdd(event);
_controller.add(event);
}
监听
因为PublishSubject
继承自Subject
,所以listen
方法在Subject
之中:
// 这里也可以发现,onListen也是controller的listen
@override
set onListen(void Function() onListenHandler) {
_controller.onListen = onListenHandler;
}
销毁
因为PublishSubject
继承自Subject
,所以close
方法在Subject
之中:
@override
Future<dynamic> close() {
if (_isAddingStreamItems) {
throw StateError(
'You cannot close the subject while items are being added from addStream');
}
/// 这里也是调用的controller的close
return _controller.close();
}
这里可能就有疑问了,如果只是提供了对Stream
的一层封装,那为什么ReactiveX
还要如此大费周章呢?那是因为对于Stream
的变换提供了很多的方法。包括buffer
、bufferCount
、bufferTest
、bufferTime
、contactWith
、debounce
、debounceTime
等等,查看更多请点我
一个监听用户输入的demo
class RxDartDemoPage extends StatefulWidget {
@override
_RxDartDemoPageState createState() => _RxDartDemoPageState();
}
class _RxDartDemoPageState extends State<RxDartDemoPage> {
PublishSubject _subject;
TextEditingController _editingController;
Stream _keywordStream;
@override
void initState() {
super.initState();
_editingController = TextEditingController();
_subject = PublishSubject();
_keywordStream = _subject
.debounceTime(Duration(milliseconds: 500))
.map((event) => "搜索关键字: $event");
_editingController.addListener(() {
_subject.add(_editingController.text);
});
}
@override
void dispose() {
_subject?.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("RxDart")),
body: Column(
children: [
TextField(
controller: _editingController,
),
SizedBox(height: 40),
StreamBuilder(
stream: _keywordStream,
builder: (context, snapshot) {
return Center(child: Text(snapshot.data ?? "请输入关键词"));
},
)
],
),
);
}
}
BLoC
BLoC
全称是Bussiness Logic Component
,是谷歌提出的一种设计模式,BLoC
利用了Flutter
响应式构建的特点,通过流的方式实现界面的异步渲染,开发者可以铜鼓BLoC
可以快速实现业务与界面的分离效果。
BLoC
主要是通过Stream
和StreamBuilder
结合实现,目的就是把UI
和逻辑分离。
demo
比如新建工程中的默认实现(点击加号,数字加一),如果使用BLoC
来实现就是新建一个类,内部提供两个对外开放的内容,一个是stream
另外一个则是add
方法,通过stream
和StreamBuilder
进行关联,当数据发生改变时主动更新UI
;通过add
方法对外公开,提供给按钮点击调用。
class BLoCDemoPage extends StatefulWidget {
@override
_BLoCDemoPageState createState() => _BLoCDemoPageState();
}
class _BLoCDemoPageState extends State<BLoCDemoPage> {
final _CountBLoC _bLoC = _CountBLoC();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("BLoC"),
),
body: Column(
children: [
Text("BLoC大多是利用Stream和StreamBuilder实现,更多的是一种设计模式的思路,好处就是分离UI和逻辑层"),
StreamBuilder(
initialData: 0,
stream: _bLoC.countStream,
builder: (context, snapshot) => Center(
child: Text(
"${snapshot.data}",
style: TextStyle(fontSize: 30, color: Colors.redAccent),
),
),
),
],
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: _bLoC.add,
),
);
}
@override
void dispose() {
_bLoC.dispose();
super.dispose();
}
}
class _CountBLoC {
int _count = 0;
StreamController<int> _streamController = StreamController<int>();
/// 提供给外界更新使用
Stream get countStream => _streamController.stream;
/// 触发更新逻辑
void add() {
_count++;
_streamController.add(_count);
}
/// 销毁
dispose() {
_streamController?.close();
}
}
流程图
[图片上传失败...(image-682214-1616915696007)]
scoped_model
scoped_model
是Flutter
中最简单的第三方状态的管理框架,它巧妙的利用了Flutter
中的一些特性,只有一个dart
的文件情况下,实现了实用的状态管理模型。使用它一般需要三步。
- 1、新建一个类并继承
Model
,并且在想要更新UI
的时候调用notifyListenrs
- 2、使用
ScopedModel
控件加载Model
- 3、使用
ScopedModelDescendant
或者ScopedModel.of<T>(context)
加载model内的数据进行显示
demo
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart' as sc;
class ScopeModelDemoPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Scope model")),
body: SafeArea(
child: Container(
child: sc.ScopedModel<_CountModel>(
model: _CountModel(),
child: sc.ScopedModelDescendant<_CountModel>(
builder: (context, child, model) {
return Column(
children: [
Expanded(child: Center(child: Text(model.count.toString()))),
Center(child: FlatButton(
onPressed: model.add,
color: Colors.blue,
child: Icon(Icons.add),
),),
],
);
},
)
),
),
),
);
}
}
class _CountModel extends sc.Model {
static _CountModel of(BuildContext context) =>
sc.ScopedModel.of<_CountModel>(context);
int _count = 0;
int get count => _count;
void add() {
_count++;
notifyListeners();
}
}
流程图
在查看ScopedModel
的源码之后,发现他首先使用AnimatedBuilder
包装起来,AnimatedBuilder
继承了AnimatedWidget
,在AnimatedWidget
的生命周期中会对Listenable
接口添加监听,而Model
恰好就实现了Listenable
接口,从而可以达到刷新的效果。ScopedModel
内部除了使用AnimatedBuilder
包装起来之外,还使用_InheritedModel
再次进行了包装,保证了数据向下传递和共享。
ScopedModel.build方法源码
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: model,
builder: (context, _) => _InheritedModel<T>(model: model, child: child),
);
}
flutter_reduce
redux
是一种单向数据流架构。在Redux
中,数据都是存储在单一信源(Store)
中,然后数据存储的时候通过Reducer
进行更新,而触发更新的动作就是Action
。之所以说他是单向数据流,那是因为redux
通过action
发出的行为,通过reducer
更新之后并把数据保存到store
中,在加上Widget
之后就变成了一个闭环,如下图:
流程图
使用流程
- 1、创建
State
- 2、创建
Action
- 3、创建
Reducer
- 4、创建/保存
Store
- 5、关联
Widget
- 6、发出
Action
,触发第4步
demo
下面用一个两个页面之前的数据更新demo
进行演示:
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
/// 1、创建State
class ReduxCountState {
int _count;
int get count => _count;
ReduxCountState(this._count);
}
/// 2、创建Action
enum ReduxAction { increment }
/// 3、创建Reducer
ReduxCountState reducer(ReduxCountState state, dynamic action) {
switch (action) {
case ReduxAction.increment:
return ReduxCountState(state.count + 1);
default:
return state;
}
}
/// 拦截器,拦截器介于Action和Reducer之间,如果我们要对一些事件进行拦截,就可以在这里处理
/// 举个例子:当我们更新用户信息的时候(假设有头像,名称),需要去刷新,当我们只更新
/// 名称的时候,由于头像没更新,我不希望头像也倍刷新一次,此时就可以根据action,进行拦截不响应处理
class ReduxCountMiddleware implements MiddlewareClass<ReduxCountState> {
@override
call(Store<ReduxCountState> store, action, next) {
/// 只更新偶数,奇数不处理
if (store.state.count % 2 != 0) {
next(action);
print("xxxxxxxxx 我是拦截器,偶数通过");
} else {
next(action);
next(action);
print("xxxxxxxxx 我是拦截器,过滤奇数");
}
}
}
class FlutterReduxDemoPage extends StatefulWidget {
@override
_FlutterReduxDemoPageState createState() => _FlutterReduxDemoPageState();
}
class _FlutterReduxDemoPageState extends State<FlutterReduxDemoPage> {
/// 4、创建Store
final store = Store<ReduxCountState>(
reducer,
/// 拦截奇数
middleware: [ReduxCountMiddleware()],
initialState: ReduxCountState(0),
);
@override
Widget build(BuildContext context) {
return StoreProvider<ReduxCountState>(
store: store,
child: Scaffold(
appBar: AppBar(
title: Text("redux"),
),
body: Center(
/// 5、关联Widget
child: StoreConnector<ReduxCountState, String>(
converter: (store) => store.state.count.toString(),
builder: (context, val) => Text(
val,
style: TextStyle(fontSize: 30, color: Colors.red),
),
),
),
/// 6、触发
floatingActionButton: StoreConnector<ReduxCountState, VoidCallback>(
converter: (store) {
return () => store.dispatch(ReduxAction.increment);
},
builder: (context, callback) {
return FloatingActionButton(
child: Icon(Icons.add),
onPressed: callback,
);
},
),
),
);
}
}
总结
redux
内部是通过InheritedWidget
和Stream
以及StreamBuilder
的自定义封装。由于他的单向数据流思想,开发者的每个操作都只是一个Action
,而这个行为所触发的逻辑完全有middleware
和reducer
决定,这样子的设计模式一定程度上将业务和UI
进行了隔离,并且规范了整个事件流过程中的调用模式。
工具很强大,但是需要花费一定的学习成本,但是如果你之前是前端开发者,那么使用redux
还是很得心应手的。但如果你之前更多的是App
开发,那可能Provider
会更接近于你的使用方式。
Provide/Provider
在Provider
之前官方推荐的状态管理方式之一就是provide,它的特点不复杂、好理解、可控度高,但是后面就被Provider
给替代了。
流程图
1、被设置到ChangeNotifierProvider
的ChangeNotifier
会被执行addListener
,添加listener
2、listener
内部会调用StateDelegate
的StateSetter
方法,从而调用到StatefulWidget
的setState
3、当执行ChangeNotifier
的notifyListeners
,最终就会触发setState
更新
使用流程
1、创建
ChangeNotifier
的子类,实现相关内部逻辑2、使用包括但不限于的:
ChangeNotfierProvider
、MultiProvider
等provider
封装好的实体类把ChangeNotifier
的子类加入到provider
之中-
3、使用
Consummer
、Provider.of(context)
等引用provider
的值进行关联如果只是想获取
provider
的值,并不想根据状态进行更新需要使用Provider.of<T>(context, listen: false)
来获取到Provider
4、调用
ChangeNotifier
的子类的notifyListeners
方法触发更新
demo
之所以说简单,我们通过官方的数字累加Demo就可以看出
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ProviderDemoPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => _CountProvider(),
child: Scaffold(
appBar: AppBar(title: Text("Provider")),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Builder(
builder: (context) {
return Text(
Provider.of<_CountProvider>(context).count.toString(),
style: TextStyle(fontSize: 30, color: Colors.orangeAccent),
);
},
),
Consumer<_CountProvider>(
builder: (context, provider, child) {
return Center(
child: Text(
provider.count.toString(),
style: TextStyle(fontSize: 30, color: Colors.orangeAccent),
),
);
},
)
],
),
floatingActionButton: Builder(
builder: (context) => FloatingActionButton(
onPressed: () {
Provider.of<_CountProvider>(context, listen: false)?.add();
},
child: Icon(Icons.add),
),
),
),
);
}
}
class _CountProvider extends ChangeNotifier {
int _count = 0;
int get count => _count;
void add() {
_count++;
notifyListeners();
}
}
总结
Provider
提供了一种简单、不复杂,可控性好的状态管理方式,通过MultiProviders
可以在一个页面中插入多个Provider
,在配合Consumer
使用可以实现颗粒度级别的刷新,避免造成不必要的性能浪费。也是目前主流的状态管理方式之一。