2022年5月

近来工作中用上了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

二 总结

  1. Provider可以定义在任意地方,其状态只提供给其子Widget访问。例如,定义在App之上可实现全局的状态共享的状态,定义在页面之上可实现页面内的状态共享。
  2. Provider的子Widget(即child参数)不能传入StatelessWidget对象,或者说不能直接直接传入Widget对象,否则后面的所有孙Widget不能通过context获取其状态。解决方案是使用builder参数,传入构建子Widget的函数,或者child参数设置带有builder函数的Widget,例如Builder对象。
  3. 数据变化,必然导致重绘。所以不要过于担心是否重绘,而重点关注重绘的点在哪里,如何减少重绘的Widget。重绘Widget,会向上找到最近的builder方法并执行。所以需要重绘的Widget,最好放在其builder方法内。需要变化的StatelessWidget对象,用Builder类的builder方法包裹,是个很好的做法。
  4. Provider是以类型区分数据的。如果是多个相同数据类型(例如int类型)的状态,则需要定义不同的类,且都含有该数据类型(例如int类型)的属性。
  5. 定义多个Provider,可以使用MultiProvider。
  6. 组合多个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的状态,有以下三种方式:

  1. read,即只读。只获取状态,不进行监听。示例代码:

    // 使用Provider.of,需要加上参数“listen: false”
    T t = Provider.of<T>(context,listen: false));
    
    // 使用context.read方法最简单
    T t = context.read<T>();
    
  2. select,即只监听指定数据。指定数据有变化,才会执行重绘。

    // 使用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));
    
  3. watch,即监听状态的变化。状态有任何变化,都会执行重绘。

    // 使用Consumer类,可以定义builder方法
    Consumer<T>(
      builder: (_, t, __) {return Text('${t.r}');}
    );
    
    // 使用Provider.of方法。如果取出的数据需要重绘,则最好用Builder类包裹一下
    T t = Provider.of<T>(context);
    
    // 使用context.watch方法最简单。如果取出的数据需要重绘,则最好用Builder类包裹一下
    T t = context.watch<T>();