Understanding Flutter Provider
近来工作中用上了Flutter,并且使用了Provider作为状态管理,确实爽,但是也踩了一下坑。
一 概述
Provider是基于InheritedWidget组件,使用观察者模式 + 生产者消费者模式,实现状态共享,简直就是为了取代StatefulWidget而存在。相关资料:
Provider的Flutter插件网址:
https://pub.dev/packages/provider
Provider的官方中文说明:
https://github.com/rrousselGit/provider/blob/master/resources/translations/zh-CN/README.md
二 总结
- Provider可以定义在任意地方,其状态只提供给其子Widget访问。例如,定义在App之上可实现全局的状态共享的状态,定义在页面之上可实现页面内的状态共享。
- Provider的子Widget(即child参数)不能传入StatelessWidget对象,或者说不能直接直接传入Widget对象,否则后面的所有孙Widget不能通过context获取其状态。解决方案是使用builder参数,传入构建子Widget的函数,或者child参数设置带有builder函数的Widget,例如Builder对象。
- 数据变化,必然导致重绘。所以不要过于担心是否重绘,而重点关注重绘的点在哪里,如何减少重绘的Widget。重绘Widget,会向上找到最近的builder方法并执行。所以需要重绘的Widget,最好放在其builder方法内。需要变化的StatelessWidget对象,用Builder类的builder方法包裹,是个很好的做法。
- Provider是以类型区分数据的。如果是多个相同数据类型(例如int类型)的状态,则需要定义不同的类,且都含有该数据类型(例如int类型)的属性。
- 定义多个Provider,可以使用MultiProvider。
- 组合多个Provider对象,可以使用ProxyProvider。
三 Provider类型
一般使用ChangeNotifierProvider就可以,更多的Provider类型如下:
类型 | 描述 | |
---|---|---|
Provider | 最基础的 provider 组成,接收一个任意值并暴露它。 | |
ListenableProvider | 供可监听对象使用的特殊 provider。ListenableProvider 会监听对象,并在监听器被调用时更新依赖此对象的 widgets。 | |
ChangeNotifierProvider | 为 ChangeNotifier 提供的 ListenableProvider 规范,会在需要时自动调用 ChangeNotifier.dispose。 | |
ValueListenableProvider | 监听 ValueListenable,并且只暴露出 ValueListenable.value。 | |
StreamProvider | 监听流,并暴露出当前的最新值。 | |
FutureProvider | 接收一个 Future,并在其进入 complete 状态时更新依赖它的组件。 |
四 监听方式
获取Provider的状态,有以下三种方式:
read,即只读。只获取状态,不进行监听。示例代码:
// 使用Provider.of,需要加上参数“listen: false”
T t = Provider.of(context,listen: false)); // 使用context.read方法最简单
T t = context.read(); select,即只监听指定数据。指定数据有变化,才会执行重绘。注意:如果监听对象(包括List对象),只有对象的内存地址变化了,才会执行重绘。对象的属性(包括List对象的元素)变化,不会引起重绘。
// 使用Selector类,可以定义builder方法
Selector<T, R>(
selector: (_, t) {return t.r;},
builder: (_, r, __) {return Text('${r}');}
);// 使用context.select方法最简单。如果取出的数据需要重绘,则最好用Builder类包裹一下
R r = context.select<T,R>(R cb(T value));// Flutter Provider Selector数据更新问题优化
// https://blog.csdn.net/Code1994/article/details/124720388watch,即监听状态的变化。状态有任何变化,都会执行重绘。
// 使用Consumer类,可以定义builder方法
Consumer(
builder: (_, t, __) {return Text('${t.r}');}
);// 使用Provider.of方法。如果取出的数据需要重绘,则最好用Builder类包裹一下
T t = Provider.of(context); // 使用context.watch方法最简单。如果取出的数据需要重绘,则最好用Builder类包裹一下
T t = context.watch();