flutter数据共享系列——随记 Provider InheritedWidget 解决了数据共享问题。迎面也带来数据刷新导致的组件不必要更新问题。Provider基于InheritedWidget实现数据共享,数据更新,定向通知组件更新等。
接下来我们先从Provider使用开始切入,逐步分析Provider的实现,以及对组件的应用进行熟悉。
就拿官方文档开始:
新建一个模型Counter : 1 2 3 4 5 6 7 8 9 10 class Counter with ChangeNotifier {   int _count = 0;   int get count => _count;   void increment() {     _count++;     notifyListeners();   } } 
 
在合适的位置初始化 这里我们选择main方法:
1 2 3 4 5 6 7 8 9 10 11 void main() {   runApp(     MultiProvider(       providers: [         ChangeNotifierProvider(create: (_) => Counter()),       ],       child: const MyApp(),     ),   ); } 
 
使用并修改数据 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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 class MyApp extends StatelessWidget {   const MyApp({Key? key}) : super(key: key);   @override   Widget build(BuildContext context) {     return const MaterialApp(       home: MyHomePage(),     );   } } class MyHomePage extends StatelessWidget {   const MyHomePage({Key? key}) : super(key: key);   @override   Widget build(BuildContext context) {     return Scaffold(       appBar: AppBar(         title: const Text('Example'),       ),       body: Center(         child: Column(           mainAxisSize: MainAxisSize.min,           mainAxisAlignment: MainAxisAlignment.center,           children: const <Widget>[             Text('You have pushed the button this many times:'),              Extracted as a separate widget for performance optimization.              As a separate widget, it will rebuild independently from [MyHomePage].                           This is totally optional (and rarely needed).              Similarly, we could also use [Consumer] or [Selector].             Count(),           ],         ),       ),       floatingActionButton: FloatingActionButton(         key: const Key('increment_floatingActionButton'),          Calls `context.read` instead of `context.watch` so that it does not rebuild          when [Counter] changes.         onPressed: () => context.read<Counter>().increment(),         tooltip: 'Increment',         child: const Icon(Icons.add),       ),     );   } } class Count extends StatelessWidget {   const Count({Key? key}) : super(key: key);   @override   Widget build(BuildContext context) {     return Text(          Calls `context.watch` to make [Count] rebuild when [Counter] changes.         '${context.watch<Counter>().count}',         key: const Key('counterState'),         style: Theme.of(context).textTheme.headline4);   } } 
 
使用注意事项 1. 共享数据定义为私有属性,提供get方法和update方法 这样可以有效的保护数据结构,统一修改入口和获取方法。
2. 适当隔离会进行rebuild的组件,常用的方式有三种: 
单独封装组件  
通过 Consumer包裹  
通过 Selector包裹 ,selector可以在某些值不变的情况下,防止rebuild。常用的地方是针对列表中个别数据进行修改。 
 
3. 区分watch和read的使用 watch和read是Provider框架内部对BuildContext的扩展类。用户获取父级组件指定数据入口。区别在于是否添加了linsten,这个关系到是否需要实时刷新。 简单区分两种场景:
watch:界面监听数据,更新页面  
read:响应业务交互,去操作更新数据 。 
 
两种方法的源码也很简单,只是为了方便生成的拓展类:
1 2 3 4 5 6 7 8 9 10 11 12 13  Exposes the [read] method. extension ReadContext on BuildContext {   T read<T>() {     return Provider.of<T>(this, listen: false);   } }  Exposes the [watch] method. extension WatchContext on BuildContext {   T watch<T>() {     return Provider.of<T>(this);   } } 
 
这里我们来看看Provider.of源码:
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   static T of<T>(BuildContext context, {bool listen = true}) { // 移出部分不必要代码     final inheritedElement = _inheritedElementOf<T>(context);     if (listen) {       context.dependOnInheritedElement(inheritedElement);     }     return inheritedElement.value;   }        static _InheritedProviderScopeElement<T> _inheritedElementOf<T>(     BuildContext context,   ) {     // 移出部分不必要代码     _InheritedProviderScopeElement<T>? inheritedElement;     if (context.widget is _InheritedProviderScope<T>) {       context.visitAncestorElements((parent) {         inheritedElement = parent.getElementForInheritedWidgetOfExactType<             _InheritedProviderScope<T>>() as _InheritedProviderScopeElement<T>?;         return false;       });     } else {       inheritedElement = context.getElementForInheritedWidgetOfExactType<           _InheritedProviderScope<T>>() as _InheritedProviderScopeElement<T>?;     }     if (inheritedElement == null) {       throw ProviderNotFoundException(T, context.widget.runtimeType);     }     return inheritedElement!;   } 
 
原来加不加listen的区别在于获取数据的方式是getElementForInheritedWidgetOfExactTypeordependOnInheritedElement。dependOnInheritedElement会新增一个注册,这个注册会调用在数据变更后,调用消费者的didChangeDependencies 。稍微具体点分析可以查看我之前的文章——记InheritedWidget使用思考
 
ChangeNotifier 实现Listenable接口的一个简单类,官方给的说明很简单:
A class that can be extended or mixed in that provides a change notification
可以扩展或混合的类,提供更改通知
 
实现了算法复杂度为O(1)去添加监听,O(N)去移除监听。对数据更新高效通知页面去刷新。provider的数据模型均得继承与它。
ChangeNotifierProvider 有了数据模型,接下来就开始创建我们的ChangeNotifier,就要用到ChangeNotifierProvider。
先说一个错误的示例 ,错误的示例 ,错误的示例 ,在build中通过ChangeNotifierProvider.value去创建:
1 2 3 4 ChangeNotifierProvider.value(   value: new MyChangeNotifier(),   child: ... ) 
 
这样会造成内存泄露和潜在的bug ——参考 。
当然,这个方法存在肯定是有他的意义的——如果你已经有了ChangeNotifier实例,就可以通过ChangeNotifierProvider.value进行构造。而不是选用create。
正确的方法是通过creat方法去构建:
1 2 3 4 ChangeNotifierProvider(   create: (_) => new MyChangeNotifier(),   child: ... ) 
 
不要传入变量去构建ChangeNotifier,这样的话,当变量更新,ChangeNotifier是不会去更新的。  1 2 3 4 5 6 int  count;ChangeNotifierProvider(   create: (_) => new  MyChangeNotifier(count),   child: ... ) 
 
如果你确实需要传入变量,请使用ChangeNotifierProxyProvider。具体ChangeNotifierProxyProvider使用,这里就不探讨了。