Flutter Provider状态管理---四种消费者使用分析

文章系列 Flutter Provider状态管理---介绍、类图分析、基本使用
Flutter Provider状态管理---八种提供者使用分析
Flutter Provider状态管理---四种消费者使用分析
Flutter Provider状态管理---MVVM架构实战
视频系列 Flutter Provider状态管理---介绍、类图分析、基本使用
Flutter Provider状态管理---八种提供者使用分析
Flutter Provider状态管理---四种消费者使用分析
Flutter Provider状态管理---MVVM架构实战
源码仓库地址 github仓库地址
前言 在上一篇文章中我们对Provider的8种提供者进行了详细的描述以及用对应的案例说明他们的区别,那么这一节我们来聊一聊Provider的消费者,如果去优化你的项目结构以及它们的使用区别。
Provider.of Provider.of(context)Provider为我们提供的静态方法,当我们使用该方法去获取值的时候会返回查找到的最近的T类型的provider给我们,而且也不会遍历整个组件树,下面我们看下代码:
第一步:定义模型
我们定义了一个CountNotifier1的模型,后面所有的示例代码将围绕该模型来演示

import 'package:flutter/material.dart'; class CountNotifier1 with ChangeNotifier {int count = 0; void increment() { count++; notifyListeners(); } }

第二步:应用程序入口设置
return ChangeNotifierProvider( create: (_) => CountNotifier1(), child: MaterialApp( debugShowCheckedModeBanner: false, home: ConsumerExample(), ), );

第三步:使用Provider.of
这里读取值和点击按钮+1时都是通过Provider.of()来获取及使用。
import 'package:flutter/material.dart'; import 'package:flutter_provider_example/consumer_example/count_notifier1.dart'; import 'package:provider/provider.dart'; class ConsumerExample extends StatelessWidget {@override Widget build(BuildContext context) {return Scaffold( appBar: AppBar( title: Text("ConsumerExample"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(Provider.of(context).count.toString(), style: TextStyle( color: Colors.red, fontSize: 50 ), ), Padding( padding: EdgeInsets.only( top: 20 ), child: ElevatedButton( onPressed: (){ Provider.of(context).increment(); }, child: Text("点击加1"), ), ) ], ), ), ); } }

错误日志
当我们运行代码的时候会提示一个报错,它提示说试图从Widget树外部监听提供者公开的值,如果要修复可以把listen改成false,这个问题其实是在Provider 4.0.2后会出现的,最主要的是它的默认行为就是ture,错误日志如下:
======== Exception caught by gesture =============================================================== The following assertion was thrown while handling a gesture: Tried to listen to a value exposed with provider, from outside of the widget tree.This is likely caused by an event handler (like a button's onPressed) that called Provider.of without passing `listen: false`.To fix, write: Provider.of(context, listen: false); It is unsupported because may pointlessly rebuild the widget associated to the event handler, when the widget tree doesn't care about the value.The context used was: ConsumerExample(dependencies: [_InheritedProviderScope]) 'package:provider/src/provider.dart': Failed assertion: line 276 pos 7: 'context.owner!.debugBuilding || listen == false || debugIsInInheritedProviderUpdate'When the exception was thrown, this was the stack: ........ ====================================================================================================

设置listen为false
Provider.of(context, listen: false).increment();

运行结果
Flutter Provider状态管理---四种消费者使用分析
文章图片

Consumer Consumber只是在Widget中调用了Prvoider.of,并将其构造实现委托给了构造器,比如我们常见的Builder,如果你的Widget依赖多个模型,那么它还提供了Consumer23456方便调用,我们接下来对上面的案例采用Consumer来修改
用Consumer包裹组件
里面有个builder构造器,当我们把body改成下面重新运行后可以发现和使用Provider.of的结果一样,但是这里不需要在像使用Provider.of那样每次使用都要写一大串的重复性代码。
里面有三个属性:
  • context: 当前的上下文
  • Provider.of(context): 模型对象
  • child: 子组件(不需要刷新的部分)
body: Consumer( builder: (_, CountNotifier1 countNotifier1, child) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(countNotifier1.count.toString(), style: TextStyle( color: Colors.red, fontSize: 50 ), ), Padding( padding: EdgeInsets.only( top: 20 ), child: ElevatedButton( onPressed: (){ countNotifier1.increment(); }, child: Text("点击加1"), ), ) ], ), ); }, ),

优化Consumer
优化方式一:尽可能调整Consumer的位置 我们在上面的代码中发现Center以及Column组件也被Consumer包裹了进来,但是这两个组件是不需要更新状态的,而我们每次构建的Widget的时候,会重建整个body,所以我们优化一下代码结构,看起来就像下面这样:
body: Center( child: Consumer( builder: (_, CountNotifier1 countNotifier1, child) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( countNotifier1.count.toString(), style: TextStyle(color: Colors.red, fontSize: 50), ), Padding( padding: EdgeInsets.only(top: 20), child: ElevatedButton( onPressed: () { countNotifier1.increment(); }, child: Text("点击加1"), ), ), Container( child: Column( children: [ Text("更多组件1"), Text("更多组件2"), Text("更多组件3"), Text("更多组件4"), Text("更多组件5"), Text("更多组件6"), ], ), ) ], ), ); }, ) )

优化方式二:不需要刷新但被Consumer包裹的组件用child 比如上面我们有更多组件1-6甚至数百个组件无需刷新状态,但由于你用Consumer包裹会导致全部刷新,那么明显会导致性能的下降,你可能会想到单独用多个Consumer包裹需要刷新的组件就解决了,但这不就是本末倒置了吗,本身Provider是解决代码的健壮、重复的代码,所以这个时候我们可以采用Consumer为我们提供的child参数,如下:
body: Center( child: Consumer( builder: (_, CountNotifier1 countNotifier1, child) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( countNotifier1.count.toString(), style: TextStyle(color: Colors.red, fontSize: 50), ), Padding( padding: EdgeInsets.only(top: 20), child: ElevatedButton( onPressed: () { countNotifier1.increment(); }, child: Text("点击加1"), ), ), child! ], ), ); }, child: Container( child: Column( children: [ Text("更多组件1"), Text("更多组件2"), Text("更多组件3"), Text("更多组件4"), Text("更多组件5"), Text("更多组件6"), ], ), ), ) ),

Selector Selector类和Consumer类似,只是对build调用Widget方法时提供更精细的控制,简单点来说,Selector也是一个消费者,它允许你可以从模型中准备定义哪些属性。
我们来举个例子:
比如,用户模型中有50个属性,但是我只需要更新年龄,这样我希望不需要重建用户名、电话号码等组件,那么Selector就是用于解决这个问题,我们看一下示例:
第一步:定义模型
import 'package:flutter/material.dart'; class UserModel6 with ChangeNotifier {String name = "Jimi"; int age = 18; String phone = "18888888888"; void increaseAge() { age++; notifyListeners(); } }

第二步:应用程序入口设置
return ChangeNotifierProvider( create: (_) => UserModel6(), child: MaterialApp( debugShowCheckedModeBanner: false, home: SelectorExample(), ), );

第三步:使用Selector更精细的控制
import 'package:flutter/material.dart'; import 'package:flutter_provider_example/selector_example/user_model6.dart'; import 'package:provider/provider.dart'; class SelectorExample extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("SelectorExample"), ), body: Center( child: Selector( selector: (_, userModel6) => userModel6.age, builder: (_, age, child) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(age.toString(), style: TextStyle( color: Colors.red, fontSize: 30 ) ), child! ], ); }, child: Padding( padding: EdgeInsets.all(20), child: ElevatedButton( onPressed: (){ Provider.of(context, listen: false).increaseAge(); }, child: Text("改变年龄"), ), ), ), ), ); } }

运行结果
Flutter Provider状态管理---四种消费者使用分析
文章图片

InheritedContext InheritedContextProvider内置扩展了BuildContext,它不保存了组件在树中自己位置的引用,我们在上面的案例中见到Provider.of(context,listen: false),其实这个of方法就是使用Flutter查找树并找到Provider子类型为CountNotifier1而已。
三大方式:
  • BuildContext.read: BuildContext.read()可以替换掉Provider.of(context,listen: false),它会找到CountNotifier1并返回它。
  • BuildContext.watch: BuildContext.watch()可以替换掉Provider.of(context,listen: false),看起来和read没有什么不同,但是使用watch你就不需要在使用Consumer
  • BuildContext.select: BuildContext.select()可以替换掉Provider.of(context,listen: false),看起来和watch也没有什么不同,但是使用select你就不需要在使用Selector
BuildContext.read
下面两种使用方式结果是一样的
使用Provider.of()
import 'package:flutter/material.dart'; import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart'; import 'package:provider/provider.dart'; class InheritedContextExample extends StatelessWidget {@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("InheritedContextExample"), ),/// Provider.of 获取值 body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(Provider.of(context).count.toString(), style: TextStyle( color: Colors.red, fontSize: 50 ), ), Padding( padding: EdgeInsets.only(top: 20), child: ElevatedButton( onPressed: () => Provider.of(context, listen: false).increment(), child: Text("点击加1"), ), ), ], ), ), ); } }

使用BuildContext.read
import 'package:flutter/material.dart'; import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart'; import 'package:provider/provider.dart'; class InheritedContextExample extends StatelessWidget {@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("InheritedContextExample"), ), /// read 获取值 body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(context.read().count.toString(), style: TextStyle( color: Colors.red, fontSize: 50 ), ), Padding( padding: EdgeInsets.only(top: 20), child: ElevatedButton( onPressed: () => Provider.of(context, listen: false).increment(), child: Text("点击加1"), ), ), ], ), ), ); } }

BuildContext.watch
使用Consumer
import 'package:flutter/material.dart'; import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart'; import 'package:provider/provider.dart'; class InheritedContextExample extends StatelessWidget {@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("InheritedContextExample"), ), /// Consumer 获取值 body: Center( child: Consumer( builder: (_, countNotifier2, child) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(countNotifier2.count.toString(), style: TextStyle( color: Colors.red, fontSize: 50 ), ), Padding( padding: EdgeInsets.only(top: 20), child: ElevatedButton( onPressed: () => countNotifier2.increment(), child: Text("点击加1"), ), ), ], ); }, ), ), ); } }

使用BuildContext.watch
import 'package:flutter/material.dart'; import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart'; import 'package:provider/provider.dart'; class InheritedContextExample extends StatelessWidget {@override Widget build(BuildContext context) {/// 重要 final countNotifier2 = context.watch(); return Scaffold( appBar: AppBar( title: Text("InheritedContextExample"), ), /// watch body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(countNotifier2.count.toString(), style: TextStyle( color: Colors.red, fontSize: 50 ), ), Padding( padding: EdgeInsets.only(top: 20), child: ElevatedButton( onPressed: () => countNotifier2.increment(), child: Text("点击加1"), ), ), ], ), ),); } }

BuildContext.select
使用Selector
import 'package:flutter/material.dart'; import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart'; import 'package:provider/provider.dart'; class InheritedContextExample extends StatelessWidget {@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("InheritedContextExample"), ), /// Selector body: Center( child: Selector( selector: (_, countNotifier2) => countNotifier2.count, builder: (_, count, child) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(count.toString(), style: TextStyle( color: Colors.red, fontSize: 50 ), ), child! ], ); }, child: Padding( padding: EdgeInsets.only(top: 20), child: ElevatedButton( onPressed: () => Provider.of(context, listen: false).increment(), child: Text("点击加1"), ), ), ), ),); } }

使用BuildContext.select
import 'package:flutter/material.dart'; import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart'; import 'package:provider/provider.dart'; class InheritedContextExample extends StatelessWidget {@override Widget build(BuildContext context) {/// 重要 final count = context.select((CountNotifier2 countNotifier2) => countNotifier2.count); return Scaffold( appBar: AppBar( title: Text("InheritedContextExample"), ), /// select body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(count.toString(), style: TextStyle( color: Colors.red, fontSize: 50 ), ), Padding( padding: EdgeInsets.only(top: 20), child: ElevatedButton( onPressed: () => Provider.of(context, listen: false).increment(), child: Text("点击加1"), ), ) ], ), ), ); } }

总结 【Flutter Provider状态管理---四种消费者使用分析】Flutter为我们提供了多种读取值的方式,上面我们对消费者四大类的一个使用和分析对比,大家可根据自己的实际应用场景去使用对应的方式。

    推荐阅读