Flutter中最值得关注的也就是状态管理了,其根本目的在于状态提升,达到的效果是方便管理状态以及子组件中共享状态。
Provider
前面Flutter状态管理我们也有探索过直接将状态提升到最顶层,但是会引起状态多层传递,散落到各个组件中,并且状态变化时会导致整个界面全部刷新,为了解决这个问题,我们可以使用InheritedWidget保存状态,在使用的地方通过ListenableBuilder+ChangeNotifier实现局部刷新。当然,使用起来可能会稍微复杂一些,于是我们将其封装成了一个MyProvider以供简化使用。
现在有这么一个三方库,它就是利用这一套逻辑进行封装,方便我们管理状态的,就是Provider。
引入
地址:https://pub.dev/packages/provider
直接在pubspec.yaml中引入最新版本的即可:
1 2
| dependencies: provider: ^6.1.5+1
|
提供状态
提供状态,实际上也就是保存状态,以方便子组件获取状态。Provider提供多种方式提供状态,同时它本身也会对状态进行管理,在合适的时候创建和销毁,以及状态变化时将状态分发给使用的子组件。
Provider
最简单也是最基础的提供一个状态值,通过Provider构造函数进行声明。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| Provider({ Key? key, required Create<T> create, Dispose<T>? dispose, bool? lazy, TransitionBuilder? builder, Widget? child, })
Provider.value({ Key? key, required T value, UpdateShouldNotify<T>? updateShouldNotify, TransitionBuilder? builder, Widget? child, })
|
状态创建的代码块是必选的,其他参数都是可选的,使用起来也是非常简单,直接将其放置在顶层作为父组件即可,子组件中就能查到对应的数据。一共两个方法可供选择使用,直接调用Provider需要在create中创建一个新的值,如果这个值已经存在,只是想要将其暴露出去的话,使用Provider.value方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| class HomeWidget extends StatelessWidget { const HomeWidget({super.key});
@override Widget build(BuildContext context) { return Provider<Color>( create: (_) => Colors.red, child: ChildWidget(), ); } }
class ChildWidget extends StatelessWidget { const ChildWidget({super.key});
@override Widget build(BuildContext context) { return Container( width: 100, height: 100, color: context.watch<Color>(), child: TextButton( onPressed: () {}, child: Center(child: Text('Button')), ), ); } }
|
上面的代码主要逻辑就是通过Provider提供了一个颜色值,然后在它子组件中能够获取到对应的值并进行使用,其实我们没必要将它们单独拆分成两个组件,合在一起也是一样的效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class HomeWidget extends StatelessWidget { const HomeWidget({super.key});
@override Widget build(BuildContext context) { return Provider<Color>( create: (_) { return Colors.red; }, child: Container( width: 100, height: 100, color: context.watch<Color>(), child: TextButton( onPressed: () {}, child: Center(child: Text('Button')), ), ), ); } }
|
注意以上逻辑,当父组件和子组件合并在一块时,运行会直接报错,而报错原因就在于我们获取状态所用的context,它并不是子组件Container的context,因此无法找到对应的状态而报错。所以我们需要拿到子组件的context,我们可以在子组件外再包一层Builder或者直接使用参数builder:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| class HomeWidget extends StatelessWidget { const HomeWidget({super.key});
@override Widget build(BuildContext context) { return Provider<Color>( create: (_) => Colors.red, builder: (context, child) => Container( width: 100, height: 100, color: context.watch<Color>(), child: TextButton( onPressed: () {}, child: Center(child: Text('Button')), ), ), ); } }
|
这种提供一个单值的方式作用不大,既然是提供状态,我们的目的肯定是一个能够变化的状态值,这种提供单值的情况下无法变化状态。或许我可以传一个ChangeNotifier呢?传ChangeNotifier当然是可以的,但是我们一般也不会这样用,我们会直接使用ChangeNotifierProvider。
ChangeNotifierProvider
ChangeNotifierProvider的参数列表和Provider是一致的,只是它限定了传递的参数类型必须是ChangeNotifier的子类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| ChangeNotifierProvider({ Key? key, required Create<T> create, bool? lazy, TransitionBuilder? builder, Widget? child, }) ChangeNotifierProvider.value({ Key? key, required T value, TransitionBuilder? builder, Widget? child, })
|
注意ChangeNotifierProvider也是提供了两个构造方法,和Provider基本上时一致的,如果对外暴露的是一个新创建的ChangeNotifier,则使用默认构造函数声明;如果这个ChangeNotifier已经存在了,我们只是想要将其暴露给外面,则使用value的构造函数。
ListenableProvider
ListenableProvider和ChangeNotifierProvider是一样的,参数列表是一模一样的,唯一的区别就是ListenableProvider限定的类型是Listenable,比ChangeNotifier更灵活一些。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ListenableProvider({ Key? key, required Create<T> create, Dispose<T>? dispose, bool? lazy, TransitionBuilder? builder, Widget? child, }) ListenableProvider.value({ Key? key, required T value, UpdateShouldNotify<T>? updateShouldNotify, TransitionBuilder? builder, Widget? child, })
|
ChangeNotifierProvider提供的是一个ChangeNotifier类型的数据,并且当该组件被移除时,会自动调用ChangeNotifier#dispose来情况监听者,它是继承自ListenableProvider的。ListenableProvider限定的数据类型是Listenable类型的,并且不会自动移除监听者,需要手动传入dispose参数来自己管理。
ValueListenableProvider
1 2 3 4 5 6
| ValueListenableProvider.value({ Key? key, required ValueListenable<T> value, UpdateShouldNotify<T>? updateShouldNotify, Widget? child, })
|
对外暴露一个ValueListenable类型的数据,注意这里只有一个.value的命名构造方法,也就是只能通过它来暴露一个数据,而不能直接创建一个数据。
FutureProvider
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| FutureProvider({ Key? key, required Create<Future<T>?> create, required T initialData, ErrorBuilder<T>? catchError, UpdateShouldNotify<T>? updateShouldNotify, bool? lazy, TransitionBuilder? builder, Widget? child, }) FutureProvider.value({ Key? key, required Future<T>? value, required T initialData, ErrorBuilder<T>? catchError, UpdateShouldNotify<T>? updateShouldNotify, TransitionBuilder? builder, Widget? child, })
|
FutureProvider的作用是提供一个创建可能比较耗时的对象,它先提供一个初始值供子组件使用,当执行完耗时操作后再返回出新的对象来更新子组件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| FutureProvider<Color>( create: (_) async { await Future.delayed(Duration(seconds: 3)); return Colors.blue; }, initialData: Colors.red, child: Builder( builder: (context) { final color = context.watch<Color>(); return Container( width: 100, height: 100, color: color, child: TextButton( onPressed: () {}, child: Center(child: Text('Button')), ), ); }, ), );
|
以上的效果就是界面显示一个红色的方块,然后过了三秒后变成蓝色。
StreamProvider
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| StreamProvider({ Key? key, required Create<Stream<T>?> create, required T initialData, ErrorBuilder<T>? catchError, UpdateShouldNotify<T>? updateShouldNotify, bool? lazy, TransitionBuilder? builder, Widget? child, }) StreamProvider.value({ Key? key, required Stream<T>? value, required T initialData, ErrorBuilder<T>? catchError, UpdateShouldNotify<T>? updateShouldNotify, bool? lazy, TransitionBuilder? builder, Widget? child, })
|
StreamProvider和FutureProvider基本上是一样的,构造方法的参数列表基本上也是一样的,只不过一个是提供的Future类型的数据,一个是提供Stream类型数据。
ProxyProvider
ProxyProvider的作用是根据外部其他状态来提供新状态,前面我们提到的各种Provider都是直接创建一个新的对象或者对外暴露已有的对象,不涉及其他Provider。而ProxyProvider却是根据别的Provider提供的对象来提供新的对象。
1 2 3 4 5 6 7 8 9 10 11 12
| ProxyProvider0({ Key? key, Create<R>? create, required R Function(BuildContext context, R? value) update, UpdateShouldNotify<R>? updateShouldNotify, Dispose<R>? dispose, bool? lazy, TransitionBuilder? builder, Widget? child, })
|
ProxyProvider0接受一个参数,该参数就是它提供的类型,其中参数create会创建一个初始值,随后会调用update来进行更新,后续依赖的对象发生变化时,都会调用update来返回一个新的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| ProxyProvider0<String>( update: (BuildContext context, String? value) { return '蓝色'; }, child: Builder( builder: (context) { final text = context.watch<String>(); return Container( width: 100, height: 100, child: TextButton( onPressed: () {}, child: Center(child: Text(text)), ), ); }, ), );
|
对于ProxyProvider0而言,它实际上是不需要依赖于别的Provider的,本身就能直接提供一个对象,类似于Provider。当然,如果想要依赖的话,也是可以的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| class CircleController with ChangeNotifier { Color _color = Colors.red;
get color => _color; set color(value) { _color = value; notifyListeners(); } }
ChangeNotifierProvider<CircleController>( create: (_) => CircleController(), child: ProxyProvider0<String>( update: (BuildContext context, String? preview) { final controller = context.watch<CircleController>(); if (controller.color == Colors.red) { return '红色'; } else { return '蓝色'; } }, child:Container() ... ), );
|
也就是我们在update的时候去依赖了其他Provider提供的对象,然后根据这个对象将其进行转换,然后返回新的数据。为了渐变,他们还提供了已经帮我们依赖好的方法,就是ProxyProvider:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| ProxyProvider({ Key? key, Create<R>? create, required ProxyProviderBuilder<T, R> update, UpdateShouldNotify<R>? updateShouldNotify, Dispose<R>? dispose, bool? lazy, TransitionBuilder? builder, Widget? child, })
typedef ProxyProviderBuilder<T, R> = R Function( BuildContext context, T value, R? previous, );
|
也就是说,如果我们需要根据一个依赖的对象来返回另一个对象,则可以使用ProxyProvider,此时在update中就能直接通过value参数访问依赖的对象了,而不需要手动去context.watch了。
如果依赖两个或对象多个对象,则使用相应后缀的方法即可:
1 2 3 4 5 6 7 8 9 10
| ProxyProvider0<R>
ProxyProvider<T, R>
ProxyProvider2<T, T2, R> ProxyProvider3<T, T2, T3, R> ProxyProvider4<T, T2, T3, T4, R> ProxyProvider5<T, T2, T3, T4, T5, R> ProxyProvider6<T, T2, T3, T4, T5, T6, R>
|
简单来说,ProxyProvider更类似于一个map转换函数,根据依赖的对象来转换成新的数据,并将其提供出去。
ListenableProxyProvider
1 2 3 4 5 6 7 8 9 10 11
| ListenableProxyProvider0({ Key? key, Create<R>? create, required R Function(BuildContext, R? previous) update, Dispose<R>? dispose, UpdateShouldNotify<R>? updateShouldNotify, bool? lazy, TransitionBuilder? builder, Widget? child, })
|
普通的ListenableProvider直接创建或者暴露一个Listenable,不涉及其他外部参数。而ListenableProxyProvider虽然也是提供一个Listenable类型的数据,但是它在创建Listenable时需要依赖外部其他的对象或者说其他Provider提供的数据。其实就和ProxyProvider是一样的,也根据依赖的对象的个数创建了一系列的类:
1 2 3 4 5 6 7 8 9 10
| ListenableProxyProvider0<R extends Listenable?>
ListenableProxyProvider<T, R extends Listenable?>
ListenableProxyProvider2<T, T2, R extends Listenable?> ListenableProxyProvider3<T, T2, T3, R extends Listenable?> ListenableProxyProvider4<T, T2, T3, T4, R extends Listenable?> ListenableProxyProvider5<T, T2, T3, T4, T5, R extends Listenable?> ListenableProxyProvider6<T, T2, T3, T4, T5, T6, R extends Listenable?>
|
ChangeNotifierProxyProvider
大体上都是同一样的逻辑,限定了提供对象类型为ChangeNotifier,但是创建ChangeNotifier时又需要依赖于外部其他对象,因此使用带proxy的方法来创建。
1 2 3 4 5 6 7 8 9 10
| ChangeNotifierProxyProvider0({ Key? key, required Create<R> create, required R Function(BuildContext, R? value) update, bool? lazy, TransitionBuilder? builder, Widget? child, })
|
它和前面的几个Proxy类型区别就是,它的create也是必选的用于创建初始值,后续当依赖的对象发生变化时会触发update,而我们在update中也不要直接创建一个新的ChangeNotifier,而是应该通过getter/setter方法来对其进行更新,然后再返回。
1 2 3 4 5 6 7 8 9 10
| ChangeNotifierProxyProvider0<R extends ChangeNotifier?>
ChangeNotifierProxyProvider<T, R extends ChangeNotifier?>
ChangeNotifierProxyProvider2<T, T2, R extends ChangeNotifier?> ChangeNotifierProxyProvider3<T, T2, T3, R extends ChangeNotifier?> ChangeNotifierProxyProvider4<T, T2, T3, T4, R extends ChangeNotifier?> ChangeNotifierProxyProvider5<T, T2, T3, T4, T5, R extends ChangeNotifier?> ChangeNotifierProxyProvider6<T, T2, T3, T4, T5, T6, R extends ChangeNotifier?>
|
MultiProvider
正常一个Provider只能提供一个值,如果想要提供多个值的话,可以使用MultiProvider,它能够组合多个Provider:
1 2 3 4 5 6 7 8 9
| MultiProvider({ Key? key, required List<SingleChildWidget> providers, Widget? child, TransitionBuilder? builder, })
|
当使用MultiProvider时,会忽略它合并的多个Provider的子组件,而是使用它自己的子组件。
1 2 3 4 5 6 7 8
| MultiProvider( providers: [ Provider(create: (_) => CircleController()), ChangeNotifierProvider(create: (_)=>MyChangeNotifier()), ], child: )
|
子组件中,可以访问providers中提供的所有的数据。
使用状态
context#watch
1 2 3
| T watch<T>() { return Provider.of<T>(this); }
|
使用Context的拓展方法watch获取到依赖的数据,并且会建立一个依赖关系,当watch的数据发生变化时,会主动进行刷新。该方法只能在StatelessWidget#build或者State#build中调用。使用方式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| class CircleController with ChangeNotifier { Color _color = Colors.red;
get color => _color; set color(value) { _color = value; notifyListeners(); } }
ChangeNotifierProvider<CircleController>( create: (_) => CircleController(), child: Builder( builder: (context) { final controller = context.watch<CircleController>(); return Container( width: 100, height: 100, color: controller.color, child: TextButton( onPressed: () { controller.color = Colors.blue; }, child: Center(child: Text('Button')), ), ); }, ), )
|
如上示例,在子组件中通过watch获取到CircleController时,会自动建立依赖关系。当点击按钮时修改了CircleController的颜色属性,Provider就会自动通知到我们触发重建,从而响应变化。
context#read
1 2 3
| T read<T>() { return Provider.of<T>(this, listen: false); }
|
read也是获取依赖的数据的,它和watch的区别就是本身不会建立依赖关系,也就是获取到状态后,即使状态发生了变化,也不会触发自身的重建,从而无法响应状态的变化。例如前面的例子中,将watch直接改为read,再点击按钮时发现是没有任何变化的。
context#select
1 2 3
| R select<T, R>(R Function(T value) selector) { ... }
|
当使用watch获取状态时,例如获取一个ChangeNotifier,此时该组件会与ChangeNotifier建立关系,当它内部的任意一个属性变化时(同时触发了notifyListeners),都会导致该组件重建。而使用select则可以有选择性的与某几个属性建立依赖关系。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| ChangeNotifierProvider<CircleController>( create: (_) => CircleController(), child: Builder( builder: (context) { final color = context.select<CircleController,Color>((value)=>value.color); return Container( width: 100, height: 100, color: color, child: TextButton( onPressed: () { context.read<CircleController>().color = Colors.blue; }, child: Center(child: Text('Button')), ), ); }, ), )
|
Provider#of
1
| static T of<T>(BuildContext context, {bool listen = true}) {...}
|
实际上,前面的watch和read都是对Provider#of方法的包装而已,他们的区别就是listen的取值。当取值为true时,表示获取状态时并且建立关联,此时也就相当于watch,当状态发生变化时会通知依赖方进行重建。取值为false时表明不会建立关联。
前面这几个方法都是通过context获取的,这就很容易造成context滥用的情况,从而导致无法完成局部刷新。也就是说,在通过watch、select、Proivider.of(context, listen:true)获取依赖值并建立关联时,是与context所属的组件建立的关联,当触发刷新时会导致context所在的组件被rebuild。
1 2 3 4 5 6 7 8 9
| class CircleController with ChangeNotifier { Color _color = Colors.red;
get color => _color; set color(value) { _color = value; notifyListeners(); } }
|
例如有一个ChangeNotifierProvider提供了上面这个CircleController,然后对应的子组件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| class MyWidget extends StatelessWidget { const MyWidget({super.key});
@override Widget build(BuildContext context) { final controller = context.watch<CircleController>();
return Column( children: [ Container( width: 80, height: 80, color: Colors.green, child: Text('One'), ), Container( width: 80, height: 80, color: controller.color, child: TextButton( onPressed: (){ controller.color = Colors.yellow; }, child: Text('Two') ), ), Container( width: 80, height: 80, color: Colors.blue, child: Text('Three'), ), ], ); } }
|
上述子组件中,包含了一个Column,然后里面放了三个Container,其中第二个Container使用了controller中的颜色,并且在点击时修改了颜色。
注意,调用watch的context是MyWidget的context,但是实际用到的却只有它内部的一个Container,但是当颜色发生变化时,刷新是刷的MyWidget,也就是说会重新调用MyWidget#build来构建。这显然与我们的预期不符了,我们应该只想要Container进行重建,其他组件包括Container内部的TextButton都不发生重建。
当然了我们可以使用Builder来获取对应的Context:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| Builder(builder: (context) { final controller = context.watch<CircleController>(); return Container( width: 80, height: 80, color: controller.color, child: TextButton( onPressed: (){ controller.color = Colors.yellow; }, child: Text('Two') ), ); }),
|
通过这种方式,可以将刷新范围限定在Builder中,也就是当点击按钮修改了颜色之后,并不会导致整个MyWidget的重建,而只会重建这个Builder。当然,这基本上已经能满足我们的需求了,实现了局部的刷新,但我们要求的可能还要更高一些,我们想只有Container重建,它内部的child不需要重建,通过这种方式就很难完成了。
Consumer
1 2 3 4 5 6
| Consumer({ Key? key, required this.builder, Widget? child, })
|
前面几种获取状态的方法都是通过context获取的,也就是说需要我们手动去watch,很容易导致context的滥用而导致刷新范围不可控。使用Consumer就可以非常容易的控制范围,例如前面的那个Container刷新问题,就可以使用Consumer进行优化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| Consumer<CircleController>( builder: (context, controller, child) { return Container( width: 80, height: 80, color: controller.color, child: TextButton( onPressed: (){ controller.color = Colors.yellow; }, child: Text('Two') ), ); }, ),
|
其实也就是避免了我们手写watch了,当然它还提供了一个child属性,我们可以将Container的child放在外面声明,然后直接应用就行了,这样刷新时就只会刷新Container,而不会刷新按钮了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| Consumer<CircleController>( builder: (context, controller, child) { return Container( width: 80, height: 80, color: controller.color, child: child, ); }, child: TextButton( onPressed: () { context.read<CircleController>().color = Colors.yellow; }, child: Text('Two'), ), ),
|
这是只依赖一个对象的Consumer,还可以选择Consumer2、Consumer3…
1 2 3 4 5 6 7 8
| Consumer<T>
Consumer2<A, B> Consumer3<A, B, C> Consumer4<A, B, C, D> Consumer5<A, B, C, D, E> Consumer6<A, B, C, D, E, F>
|
Selector
前面的Consumer其实对应的就是watch的封装,它观察的是整个控制ChangeNotifier,不论它内部的任何属性变化,只要调用了notifyListeners就会导致组件的重建。而可能有时候我们这个组件只使用了一个字段,只需要这个字段变化时重建就行,而不关注其他字段。
Selector其实做的就是这个工作,也就是对select的封装而已:
1 2 3 4 5 6 7 8 9
| Selector({ Key? key, required ValueWidgetBuilder<S> builder, required S Function(BuildContext, A) selector, ShouldRebuild<S>? shouldRebuild, Widget? child, })
|
同样的,还是对前面的那个Container进行修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| Selector<CircleController, Color>( selector: (context, controller) => controller.color, builder: (context, color, child) { return Container( width: 80, height: 80, color: color, child: child, ); }, child: TextButton( onPressed: () { context.read<CircleController>().color = Colors.yellow; }, child: Text('Two'), ), )
|
就和Consumer依赖多个对象一样,Selector也支持依赖多个对象,使用方式也是一样的:
1 2 3 4 5 6 7 8 9 10
| Selector0<T>
Selector<A, S>
Selector2<A, B, S> Selector3<A, B, C, S> Selector4<A, B, C, D, S> Selector5<A, B, C, D, E, S> Selector6<A, B, C, D, E, F, S>
|
总结
Provider作为状态管理,主要做的也就是提供状态和访问状态。提供状态用了多种Provider,实际中我们还是用的ChangeNotifierProvider比较多一些,当通过这些Provider提供状态后,子组件中就能够通过context获取到这些状态,实现对状态的使用和修改。
访问状态可以通过context获取,通常我们获取时会与状态建立关联,以便在状态发生变化时重建。而使用context获取容易造成滥用,所以通常会使用一些组件来获取状态,如Consumer和Selector。