Flutter开发 Provider详解

一、为什么会有 Provider ?

因为 Flutter 与 React 技术栈的相似性,所以在 Flutter 中涌现了诸如flutter_redux 、flutter_dva 、 flutter_mobx 、 fish_flutter等前端式的状态管理,它们大多比较复杂,而且需要对框架概念有一定理解。

而作为 Flutter 官方推荐的状态管理 scoped_model ,又因为其设计较为简单,有些时候不适用于复杂的场景。

所以在经历了一端坎坷之后,今年 Google I/O 大会之后, Provider 成了 Flutter 官方新推荐的状态管理方式之一。

它的特点就是: 不复杂,好理解,代码量不大的情况下,可以方便组合和控制刷新颗粒度 , 而原 Google 官方仓库的状态管理 flutter-provide 已宣告GG , provider 成了它的替代品。


二、Provider知识点

1、 Provider 的内部 DelegateWidget 是一个 StatefulWidget ,所以可以更新且具有生命周期。

2、状态共享是使用了 InheritedProvider 这个 InheritedWidget 实现的。

3、巧妙利用 MultiProvider 和 Consumer 封装,实现了组合与刷新颗粒度控制。


三、Provider的工作原理

1、Delegate

既然是状态管理,那么肯定有 StatefulWidget 和 setState 调用。

在 Provider 中,一系列关于  StatefulWidget 的生命周期管理和更新,都是通过各种代理完成的,如下图所示,上面代码中我们用到的 ChangeNotifierProvider 大致经历了这样的流程:

设置到 ChangeNotifierProvider 的 ChangeNotifer 会被执行 addListener 添加监听 listener。

listener 内会调用 StateDelegate 的 StateSetter 方法,从而调用到  StatefulWidget 的 setState。

当我们执行 ChangeNotifer 的 notifyListeners 时,就会最终触发 setState 更新。


2、InheritedProvider

状态共享肯定需要 InheritedWidget ,InheritedProvider 就是InheritedWidget 的子类,所有的 Provider 实现都在 build 方法中使用 InheritedProvider 进行嵌套,实现 value 的共享。


使用方式

ChangeNotifierProvider 方式

通过调用ChangeNotifier.notifyListenersChangeNotifier进行监听,将其公开给它的子Widget并重建依赖项;

1. 绑定数据

ChangeNotifierProvider绑定数据有两种方式:

ChangeNotifierProvider({Key key, @required ValueBuilder<T> builder, Widget child })

通过构造器创建一个ChangeNotifier,在ChangeNotifierProvider移除时自动处理;

classMyAppextendsStatelessWidget{@overrideWidgetbuild(BuildContext context){returnChangeNotifierProvider<User>(builder:(_)=>User('Flutter',0),child:MaterialApp(title:'Flutter Demo',theme:ThemeData(primarySwatch:Colors.blue),home:MyHomePage(title:'Peovider Demo')));}}

ChangeNotifierProvider.value({Key key, @required T notifier, Widget child })

通过监听通知给子Widget并重建依赖项;

classMyAppextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){returnChangeNotifierProvider<User>.value(notifier:User('Flutter',0),child:MaterialApp(title:'Flutter Demo',theme:ThemeData(primarySwatch:Colors.blue),home:MyHomePage(title:'Peovider Demo')));}}

2.ListenableProvider 方式

1. 数据绑定

ListenableProvider({Key key, @required ValueBuilder<T> builder, Disposer<T> dispose, Widget child })

通过构造器绑定数据并进行监听,当从Widget Tree中删除时dispose要销毁;注意:构造器builder不可为空;

classMyAppextendsStatelessWidget{@overrideWidgetbuild(BuildContext context){returnListenableProvider<User>(builder:(_)=>User('Flutter',0),child:MaterialApp(title:'Flutter Demo',theme:ThemeData(primarySwatch:Colors.blue),home:MyHomePage(title:'Peovider Demo')));}}

ListenableProvider.value({Key key, @required T listenable, Widget child })

通过.value方式对数据进行监听listenable

classMyAppextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){returnListenableProvider<User>.value(listenable:User('Flutter',0),child:MaterialApp(title:'Flutter Demo',theme:ThemeData(primarySwatch:Colors.blue),home:MyHomePage(title:'Peovider Demo')));}}

2. 获取数据

Provider.of(context)方式

classProviderTextextendsStatelessWidget{@overrideWidgetbuild(BuildContext context){finaluser=Provider.of<User>(context);returnText('${user.getName}');}}

Consumer Widget构造器方式

classProviderTextextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){returnConsumer<User>(builder:(context,user,_){returnText(user.getName);});}}


小结

为方便理解,结合上一节的ChangeNotifierProvider,发现与ListenableProviderValueListenableProvider的使用基本相同;

classChangeNotifierProvider<TextendsChangeNotifier>extendsListenableProvider<T>implementsSingleChildCloneableWidget{}classChangeNotifierimplementsListenable{}classValueListenableProvider<T>extendsAdaptiveBuilderWidget<ValueListenable<T>,ValueNotifier<T>>implementsSingleChildCloneableWidget{}classValueNotifier<T>extendsChangeNotifierimplementsValueListenable<T>{}

分析源码:ChangeNotifierProvider继承自ListenableProvider且对应的ChangeNotifier继承自listenable;算是ListenableProvider的子类;ValueNotifier继承自ChangeNotifier也与ChangeNotifierProvider相似;

使用ChangeNotifierProviderValueListenableProvider绑定实体类时需要注意分别继承对应的ChangeNotifierValueNotifier

classUserwithChangeNotifier{}classPersonextendsValueNotifier<User>{}

无论使用那种.value方式,均建议在dispose中进行listener的关闭;

@overridevoiddispose(){stream.dispose();super.dispose();}



Provider分类

你也可以在 main 方法中通过下面这行代码来禁用此提示。 Provider.debugCheckInvalidValueType = null;

这是由于 Provider 只能提供恒定的数据,不能通知依赖它的子部件刷新。提示也说的很清楚了,假如你想使用一个会发生 change 的 Provider,请使用下面的 Provider。

ListenableProvider

ChangeNotifierProvider

ValueListenableProvider

StreamProvider

这几个 Provider 有什么异同。

先关注 ListenableProvider / ChangeNotifierProvider 这两个类。

ListenableProvider 提供(provide)的对象是继承了 Listenable 抽象类的子类。由于无法混入,所以通过继承来获得 Listenable 的能力,同时必须实现其 addListener / removeListener 方法,手动管理收听者。显然,这样太过复杂,我们通常都不需要这样做。

class ChangeNotifier implements Listenable

而混入了 ChangeNotifier 的类自动帮我们实现了听众管理,所以 ListenableProvider 同样也可以接收混入了 ChangeNotifier 的类。

ChangeNotifierProvider 则更为简单,它能够对子节点提供一个 继承 / 混入 / 实现 了 ChangeNotifier 的类。通常我们只需要在 Model 中 with ChangeNotifier ,然后在需要刷新状态的时候调用 notifyListeners 即可。

那么 ChangeNotifierProvider 和 ListenableProvider 究竟区别在哪呢,ChangeNotifierProvider 会在你需要的时候,自动调用其 _disposer 方法。

static void _disposer(BuildContext context, ChangeNotifier notifier) => notifier?.dispose();

我们可以在 Model 中重写 ChangeNotifier 的 dispose 方法,来释放其资源。

ValueListenableProvider。

ValueListenableProvider 用于提供实现了 继承 / 混入 / 实现 了 ValueListenable 的 Model。它实际上是专门用于处理只有一个单一变化数据的 ChangeNotifier。

class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T>

通过 ValueListenable 处理的类不再需要数据更新的时候调用 notifyListeners。

StreamProvider

StreamProvider 专门用作提供(provide)一条 Single Stream。我在这里仅对其核心属性进行讲解。

T initialData:你可以通过这个属性声明这条流的初始值。

ErrorBuilder<T> catchError:这个属性用来捕获流中的 error。在这条流 addError 了之后,你会能够通过 T Function(BuildContext context, Object error) 回调来处理这个异常数据。实际开发中它非常有用。

updateShouldNotify:和之前的回调一样,这里不再赘述。

除了这三个构造方法都有的属性以外,StreamProvider 还有三种不同的构造方法。

StreamProvider(...):默认构造方法用作创建一个 Stream 并收听它。

StreamProvider.controller(...):通过 builder 方式创建一个 StreamController<T>。并且在 StreamProvider 被移除时,自动释放 StreamController。

StreamProvider.value(...):监听一个已有的 Stream 并将其 value 提供给子孙节点。



注意事项

不要所有状态都放在全局

开发者为了图方便省事,经常把所有东西都放在顶层 MaterialApp 之上。严格区分你的全局数据与局部数据,资源不用了就要释放!否则将会严重影响你的应用 performance。

尽量在 Model 中使用私有变量“_”,减少耦合

控制你的刷新范围

在 Flutter 中,组合大于继承的特性随处可见。常见的 Widget 实际上都是由更小的 Widget 组合而成,直到基本组件为止。为了使我们的应用拥有更高的性能,控制 Widget 的刷新范围便显得至关重要。尽量使用 Consumer 来获取祖先 Model,以维持最小刷新范围。

Provider 是如何做到状态共享的

这个问题实际上得分两步。

获取顶层数据

实际上在祖先节点中共享数据这件事我们已经在之前的文章中接触过很多次了,都是通过系统的 InheritedWidget 进行实现的。Provider 也不例外,在所有 Provider 的 build 方法中,返回了一个 InheritedProvider。

class InheritedProvider<T> extends InheritedWidget

Flutter 通过在每个 Element 上维护一个 InheritedWidget 哈希表来向下传递 Element 树中的信息。通常情况下,多个 Element 引用相同的哈希表,并且该表仅在 Element 引入新的 InheritedWidget 时改变。时间复杂度为 O(1) 。

通知刷新

通知刷新这一步实际上就是使用了 Listener 模式。Model 中维护了一堆听众,然后 notifiedListener 通知刷新。

全局状态需要放在顶层 MaterialApp 之上,优先初始化,以便在 Navigator 以及 BuildContex控制全局状态

数据初始化

全局数据

当需要获取全局顶层数据,并需要做一些会产生额外结果的时候,main 函数是一个很好的选择。在 main 方法中创建 Model 并进行初始化的工作,这样就只会执行一次。

单页面

如果我们的数据只是在这个页面中需要使用,那么你有这两种方式可以选择。

StatefulWidget

在InitState()中使用 Provider.of<T>(context)是错误的。

/// If [listen] is `true` (default), later value changes will trigger a new

/// [State.build] to widgets, and [State.didChangeDependencies] for

/// [StatefulWidget].

源码中的注释解释了,如果这个 Provider.of<T>(context) listen 了的话,那么当 notifyListeners 的时候,就会触发 context 所对应的 State 的 [State.build] 和 [State.didChangeDependencies] 方法。也就是说,如果你使用了非 Provider 提供的数据,例如 ChangeNotifierProvider 这样会改变依赖的类,并且获取数据时 Provider.of<T>(context, listen: true) 选择 listen (默认就为 listen)的话,数据刷新时会重新运行 didChangeDependencies 和 build 两个方法。这样一来对 didChangeDependencies 也会产生副作用。假如在这里请求了数据,当数据到来的时候,又回触发下一次请求,最终无限请求下去。

这里除了副作用以外还有一点,假如数据改变是一个同步行为,例如这里的 counter.increment 这样的方法,在 didChangeDependencies 中调用的话,就会造成下面这个错误。



源码分析

Flutter 中的 Builder 模式

在 Provider 中,各种 Provider 的原始构造方法都有一个 builder 参数,这里一般就用 (_) => XXXModel() 就行了。感觉有点多次一举,为什么不能像 .value() 构造方法那样简洁呢。

实际上,Provider 为了帮我们管理 Model,使用到了 delegation pattern。

builder 声明的 ValueBuilder 最终被传入代理类 BuilderStateDelegate / SingleValueDelegate。 然后通过代理类才实现的 Model 生命周期管理。