flutter中状态管理是重中之重,每当谈这个话题,总有说不完的话。

在正式介绍 Provider 为什么我们需要状态管理。如果你已经对此十分清楚,那么建议直接跳过这一节。
如果我们的应用足够简单,Flutter 作为一个声明式框架,你或许只需要将 数据 映射成 视图 就可以了。你可能并不需要状态管理,就像下面这样。
Flutter状态管理之Provider - 图1
但是随着功能的增加,你的应用程序将会有几十个甚至上百个状态。这个时候你的应用应该会是这样。
Flutter状态管理之Provider - 图2
这又是什么鬼。我们很难再清楚的测试维护我们的状态,因为它看上去实在是太复杂了!而且还会有多个页面共享同一个状态,例如当你进入一个文章点赞,退出到外部缩略展示的时候,外部也需要显示点赞数,这时候就需要同步这两个状态。
Flutter 实际上在一开始就为我们提供了一种状态管理方式,那就是 StatefulWidget。但是我们很快发现,它正是造成上述原因的罪魁祸首。
State 属于某一个特定的 Widget,在多个 Widget 之间进行交流的时候,虽然你可以使用 callback 解决,但是当嵌套足够深的话,我们增加非常多可怕的垃圾代码。
这时候,我们便迫切的需要一个架构来帮助我们理清这些关系,状态管理框架应运而生。

Provider 是什么

通过使用Provider而不用手动编写InhertedWidget,您将获取自动分配、延迟加载、大大减少每次创建新类的代码。

首先在yaml中添加,具体版本号参考:官方Provider pub,当前版本号是4.1.3.

  1. Provider: ^4.1.3

然后运行

  1. flutter pub get

获取到最新的包到本地,在需要的文件夹内导入

  1. import 'package:provider/provider.dart';

简单例子

我们还用点击按钮新增数字的例子

首先创建存储数据的Model

  1. class ProviderModel extends ChangeNotifier {
  2. int _count=0;
  3. ProviderModel();
  4. void plus() {
  5. /// 在数据变动的时候通知监听者刷新UI
  6. _count = _count + 1;
  7. notifyListeners();
  8. }
  9. }

构造view

  1. /// 使用Consumer来监听全局刷新UI
  2. Consumer<ProviderModel>(
  3. builder:
  4. (BuildContext context, ProviderModel value, Widget child) {
  5. print('Consumer 0 刷新');
  6. _string += 'c0 ';
  7. return _Row(
  8. value: value._count.toString(),
  9. callback: () {
  10. context.read<ProviderModel>().plus();
  11. },
  12. );
  13. },
  14. child: _Row(
  15. value: '0',
  16. callback: () {
  17. context.read<ProviderModel>().plus();
  18. },
  19. ),
  20. )

测试下看下效果:

Flutter状态管理之Provider - 图3

单个Model多个小部件分别刷新(局部刷新)

单个model实现单个页面多个小部件分别刷新,是使用Selector<Model,int>来实现,首先看下构造函数:

  1. class Selector<A, S> extends Selector0<S> {
  2. /// {@macro provider.selector}
  3. Selector({
  4. Key key,
  5. @required ValueWidgetBuilder<S> builder,
  6. @required S Function(BuildContext, A) selector,
  7. ShouldRebuild<S> shouldRebuild,
  8. Widget child,
  9. }) : assert(selector != null),
  10. super(
  11. key: key,
  12. shouldRebuild: shouldRebuild,
  13. builder: builder,
  14. selector: (context) => selector(context, Provider.of(context)),
  15. child: child,
  16. );
  17. }

可以看到Selector继承了Selector0,再看Selector关键build代码:

  1. class _Selector0State<T> extends SingleChildState<Selector0<T>> {
  2. T value;
  3. Widget cache;
  4. Widget oldWidget;
  5. @override
  6. Widget buildWithChild(BuildContext context, Widget child) {
  7. final selected = widget.selector(context);
  8. var shouldInvalidateCache = oldWidget != widget ||
  9. (widget._shouldRebuild != null &&
  10. widget._shouldRebuild.call(value, selected)) ||
  11. (widget._shouldRebuild == null &&
  12. !const DeepCollectionEquality().equals(value, selected));
  13. if (shouldInvalidateCache) {
  14. value = selected;
  15. oldWidget = widget;
  16. cache = widget.builder(
  17. context,
  18. selected,
  19. child,
  20. );
  21. }
  22. return cache;
  23. }
  24. }

根据我们传入的_shouldRebuild来判断是否需要更新,如果需要更新则执行widget.build(context,selected,child),否则返回已经缓存的cache.当没有_shouldRebuild参数时则根据widget.selector(ctx)的返回值判断是否和旧值相等,不等则更新UI

所以我们不写shouldRebuild也是可以的。

局部刷新用法

  1. Widget build(BuildContext context) {
  2. print('page 1');
  3. _string += 'page ';
  4. return Scaffold(
  5. appBar: AppBar(
  6. title: Text('Provider 全局与局部刷新'),
  7. ),
  8. body: Center(
  9. child: Column(
  10. mainAxisAlignment: MainAxisAlignment.center,
  11. crossAxisAlignment: CrossAxisAlignment.center,
  12. children: <Widget>[
  13. Text('全局刷新<Consumer>'),
  14. Consumer<ProviderModel>(
  15. builder:
  16. (BuildContext context, ProviderModel value, Widget child) {
  17. print('Consumer 0 刷新');
  18. _string += 'c0 ';
  19. return _Row(
  20. value: value._count.toString(),
  21. callback: () {
  22. context.read<ProviderModel>().plus();
  23. },
  24. );
  25. },
  26. child: _Row(
  27. value: '0',
  28. callback: () {
  29. context.read<ProviderModel>().plus();
  30. },
  31. ),
  32. ),
  33. SizedBox(
  34. height: 40,
  35. ),
  36. Text('局部刷新<Selector>'),
  37. Selector<ProviderModel, int>(
  38. builder: (ctx, value, child) {
  39. print('Selector 1 刷新');
  40. _string += 's1 ';
  41. return Row(
  42. mainAxisAlignment: MainAxisAlignment.center,
  43. children: <Widget>[
  44. Text('Selector<Model,int>次数:' + value.toString()),
  45. OutlineButton(
  46. onPressed: () {
  47. context.read<ProviderModel>().plus2();
  48. },
  49. child: Icon(Icons.add),
  50. )
  51. ],
  52. );
  53. },
  54. selector: (ctx, model) => model._count2,
  55. shouldRebuild: (m1, m2) {
  56. print('s1:$m1 $m2 ${m1 != m2 ? '不相等,本次刷新' : '数据相等,本次不刷新'}');
  57. return m1 != m2;
  58. },
  59. ),
  60. SizedBox(
  61. height: 40,
  62. ),
  63. Text('局部刷新<Selector>'),
  64. Selector<ProviderModel, int>(
  65. selector: (context, model) => model._count3,
  66. shouldRebuild: (m1, m2) {
  67. print('s2:$m1 $m2 ${m1 != m2 ? '不相等,本次刷新' : '数据相等,本次不刷新'}');
  68. return m1 != m2;
  69. },
  70. builder: (ctx, value, child) {
  71. print('selector 2 刷新');
  72. _string += 's2 ';
  73. return Row(
  74. mainAxisAlignment: MainAxisAlignment.center,
  75. children: <Widget>[
  76. Text('Selector<Model,int>次数:' + value.toString()),
  77. OutlineButton(
  78. onPressed: () {
  79. ctx.read<ProviderModel>().plus3();
  80. },
  81. child: Icon(Icons.add),
  82. )
  83. ],
  84. );
  85. },
  86. ),
  87. SizedBox(
  88. height: 40,
  89. ),
  90. Text('刷新次数和顺序:↓'),
  91. Text(_string),
  92. Row(
  93. mainAxisAlignment: MainAxisAlignment.center,
  94. children: <Widget>[
  95. OutlineButton(
  96. child: Icon(Icons.refresh),
  97. onPressed: () {
  98. setState(() {
  99. _string += '\n';
  100. });
  101. },
  102. ),
  103. OutlineButton(
  104. child: Icon(Icons.close),
  105. onPressed: () {
  106. setState(() {
  107. _string = '';
  108. });
  109. },
  110. )
  111. ],
  112. )
  113. ],
  114. ),
  115. ),
  116. );
  117. }

效果:

Flutter状态管理之Provider - 图4

当我们点击局部刷新s1,执行s1builds1不相等,s2相等不刷新。输出:

  1. flutter: s25 5 数据相等,本次不刷新
  2. flutter: s16 7 不相等,本次刷新
  3. flutter: Selector 1 刷新
  4. flutter: Consumer 0 刷新

当点击s2,s2的值不相等刷新UI,s1数据相等,不刷新UI.

  1. flutter: s22 3 不相等,本次刷新
  2. flutter: selector 2 刷新
  3. flutter: s10 0 数据相等,本次不刷新
  4. flutter: Consumer 0 刷新

可以看到上边2次Consumer每次都刷新了,我们探究下原因。

Consumer 全局刷新

Consumer继承了SingleCHildStatelessWidget,当我们在ViewModel中调用notification则当前widget被标记为dirty,然后在build中执行传入的builder函数,在下帧则会刷新UI

Selector<T,S>则被标记dirty时执行_Selector0State中的buildWithChild(ctx,child)函数时,根据selected_shouldRebuild来判断是否需要执行widget.builder(ctx,selected,child)(刷新UI).

其他用法

多model写法

只需要在所有需要model的上级包裹即可,当我们一个page需要2model的时候,我么通常这样子写:

  1. class BaseProviderRoute extends StatelessWidget {
  2. BaseProviderRoute({Key key}) : super(key: key);
  3. @override
  4. Widget build(BuildContext context) {
  5. return MultiProvider(
  6. providers: [
  7. ChangeNotifierProvider<ProviderModel>(
  8. create: (_) => ProviderModel(),
  9. ),
  10. ChangeNotifierProvider<ProviderModel2>(create: (_) => ProviderModel2()),
  11. ],
  12. child: BaseProvider(),
  13. );
  14. }
  15. }

当然是用的时候和单一model一致的。

  1. Selector<ProviderModel2, int>(
  2. selector: (context, model) => model.value,
  3. builder: (ctx, value, child) {
  4. print('model2 s1 刷新');
  5. _string += 'm2s1 ';
  6. return Row(
  7. mainAxisAlignment: MainAxisAlignment.center,
  8. children: <Widget>[
  9. Text('Selector<Model2,int>次数:' + value.toString()),
  10. OutlineButton(
  11. onPressed: () {
  12. ctx.read<ProviderModel2>().add(2);
  13. },
  14. child: Icon(Icons.add),
  15. )
  16. ],
  17. );
  18. },
  19. ),

watch && read

watch源码是Provider.of<T>(this),默认Provider.of<T>(this)listen=true.

  1. static T of<T>(BuildContext context, {bool listen = true}){
  2. final inheritedElement = _inheritedElementOf<T>(context);
  3. if (listen) {
  4. context.dependOnInheritedElement(inheritedElement);
  5. }
  6. return inheritedElement.value;
  7. }

read源码是Provider.of<T>(this, listen: false),watch/read只是写法简单一点,并无高深结构。

当我们想要监听值的变化则是用watch,当想调用model的函数时则使用read

参考