背景
跨页面/组件共享状态的管理方式比较多,比如全局时间总线EventBus,它是一个第三方的插件,使用观察者模式实现,通过它可以实现跨组件状态同步。但是它有两个缺点:
- 必须显示定义各种事件,不好管理
- 需要手动注册和取消注册,避免内存泄漏
手动编写简单的Provider
由于官方提供的provider功能比较全面,很多冗余的逻辑影响我们的理解。这里我们先按照Provider的逻辑实现,先手动吧编写一个简单的Provider。
分为以下几步进行:
- 自定义InheritedWidget
需要一个保存共享数据的InheritedWidget
,由于具体业务数据类型不可预期,为了通用性,我们使用泛型,定义一个通用的InheritedProvider
类,它继承自InheritedWidget
:
// 一个通用的InheritedWidget,保存需要跨组件共享的状态
class InheritedProvider<T> extends InheritedWidget {
InheritedProvider({
required this.data,
required Widget child,
}) : super(child: child);
final T data;
@override
bool updateShouldNotify(InheritedProvider<T> old) {
//在此简单返回true,则每次更新都会调用依赖其的子孙节点的`didChangeDependencies`。
return true;
}
}
- 利用Flutter提供的
ChangeNotifier
类,实现订阅者类
//将要共享的状态放到一个Model类中,然后让它继承自ChangeNotifier,这样当共享的状态改变时,我们只需要调用notifyListeners() 来通知订阅者,然后由订阅者来重新构建InheritedProvider
class ChangeNotifierProvider<T extends ChangeNotifier> extends StatefulWidget {
ChangeNotifierProvider({
Key? key,
required this.data,
required this.child,
});
final Widget child;
final T data;
//定义一个便捷方法,方便子树中的widget获取共享数据
static T? of<T>(BuildContext context) {
// final type = _typeOf<InheritedProvider<T>>();
final provider =
context.dependOnInheritedWidgetOfExactType<InheritedProvider<T>>();
return provider?.data;
}
@override
_ChangeNotifierProviderState<T> createState() =>
_ChangeNotifierProviderState<T>();
}
class _ChangeNotifierProviderState<T extends ChangeNotifier>
extends State<ChangeNotifierProvider<T>> {
void update() {
//如果数据发生变化(model类调用了notifyListeners),重新构建InheritedProvider
setState(() => {});
}
@override
void didUpdateWidget(ChangeNotifierProvider<T> oldWidget) {
//当Provider更新时,如果新旧数据不"==",则解绑旧数据监听,同时添加新数据监听
if (widget.data != oldWidget.data) {
oldWidget.data.removeListener(update);
widget.data.addListener(update);
}
super.didUpdateWidget(oldWidget);
}
@override
void initState() {
// 给model添加监听器
widget.data.addListener(update);
super.initState();
}
@override
void dispose() {
// 移除model的监听器
widget.data.removeListener(update);
super.dispose();
}
@override
Widget build(BuildContext context) {
return InheritedProvider<T>(
data: widget.data,
child: widget.child,
);
}
}
这里我们需要注意的是data类型T是继承自ChangeNotifier,widget.data的观察者其实就是update方法,当我们的data数据变化的时候就会通知观察者,调用到了update方法,update方法内部调用是setState()
方法,从而触发了刷新。所以最终_ChangeNotifierProviderState
类通过监听到共享状态(model)改变时重新构建Widget树的目的就达到了。
举个例子
就是用最开始新建Flutter Demo的时候 ,那个计数器的例子
- 定义
Counter
类,用用于表示数据信息
class Counter with ChangeNotifier {//1
int _count;
Counter(this._count);
void add() {
_count++;
notifyListeners();//2
}
get count => _count;//3
}
- 使用
class _MyHomePageState extends State<MyHomePageWidget> {
late PointerEvent _event;
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: Center(
child: Container(
width: 200,
height: 200,
color: Colors.yellow,
//ChangeNotifierProvider<Counter>
child: ChangeNotifierProvider<Counter>(
data: Counter(1),
child: Column(
children: [
// 这里一定要用Builder(builder: (context) {}包裹,不然:ChangeNotifierProvider.of<Counter>(context)为null
Builder(builder: (context) {
return Text(
"count:${ChangeNotifierProvider.of<Counter>(context)?.count}");
}),
Builder(builder: (context) {
var counter = ChangeNotifierProvider.of<Counter>(context);
return ElevatedButton(
onPressed: () {
counter?.add();
},
child: Text("点我+1"));
}),
],
),
),
)),
);
}
}
效果:
每次点击’点我+1’,count就会自增1。
其实这个只是个简单的demo,在真正的大型应用当中会有跨页面的应用场景,就能体现出它的优势了。我们只需要将ChangeNotifierProvider
应用到根Widget树上,那么整个APP就可以共享数据了。
原理
来来来重点,根据上面的简单例子,我们梳理下Provider的原理:
Module变化后会自动触发notifyListeners(),调用到订阅者ChangeNotifierProvider
的update方法,ChangeNotifierProvider
内部会重新构建InheritedWidget
,而依赖该InheritedWidget
的子孙Widget就会更新。
总结
通过上面简单的例子我们基本上了解了Provider的整体工作流程,其实官方提供的Provider还有很多其他实用的功能,比如:同时监听多个Module,代码可读性更好,需要详细了解其功能的可以查看:https://github.com/rrousselGit/provider/。provider的用法这里就不做介绍了。