文章系列
Flutter Provider状态管理---介绍、类图分析、基本使用
Flutter Provider状态管理---八种提供者使用分析
Flutter Provider状态管理---四种消费者使用分析
Flutter Provider状态管理---MVVM架构实战
视频系列
Flutter Provider状态管理---介绍、类图分析、基本使用
Flutter Provider状态管理---八种提供者使用分析
Flutter Provider状态管理---四种消费者使用分析
Flutter Provider状态管理---MVVM架构实战
源码仓库地址
前言
在上一篇文章中我们对Provider
的8种提供者进行了详细的描述以及用对应的案例说明他们的区别,那么这一节我们来聊一聊Provider
的消费者,如果去优化你的项目结构以及它们的使用区别。
Provider.of
Provider.of<T>(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<T>()
来获取及使用。
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<CountNotifier1>(context).count.toString(),
style: TextStyle(
color: Colors.red,
fontSize: 50
),
),
Padding(
padding: EdgeInsets.only(
top: 20
),
child: ElevatedButton(
onPressed: (){
Provider.of<CountNotifier1>(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<CountNotifier1>(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<CountNotifier1?>])
'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<CountNotifier1>(context, listen: false).increment();
运行结果
Consumer
Consumber
只是在Widget
中调用了Prvoider.of
,并将其构造实现委托给了构造器,比如我们常见的Builder
,如果你的Widget
依赖多个模型,那么它还提供了Consumer23456
方便调用,我们接下来对上面的案例采用Consumer
来修改
用Consumer包裹组件
里面有个builder
构造器,当我们把body
改成下面重新运行后可以发现和使用Provider.of
的结果一样,但是这里不需要在像使用Provider.of
那样每次使用都要写一大串的重复性代码。
里面有三个属性:
- context: 当前的上下文
- Provider.of<T>(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<UserModel6, int>(
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<UserModel6>(context, listen: false).increaseAge();
},
child: Text("改变年龄"),
),
),
),
),
);
}
}
运行结果
InheritedContext
InheritedContext
是Provider
内置扩展了BuildContext
,它不保存了组件在树中自己位置的引用,我们在上面的案例中见到Provider.of<CountNotifier1>(context,listen: false)
,其实这个of
方法就是使用Flutter
查找树并找到Provider
子类型为CountNotifier1
而已。
三大方式:
-
BuildContext.read:
BuildContext.read<CountNotifier1>()
可以替换掉Provider.of<CountNotifier1>(context,listen: false)
,它会找到CountNotifier1
并返回它。 -
BuildContext.watch:
BuildContext.watch<CountNotifier1>()
可以替换掉Provider.of<CountNotifier1>(context,listen: false)
,看起来和read
没有什么不同,但是使用watch
你就不需要在使用Consumer
。 -
BuildContext.select:
BuildContext.select<CountNotifier1>()
可以替换掉Provider.of<CountNotifier1>(context,listen: false)
,看起来和watch
也没有什么不同,但是使用select
你就不需要在使用Selector
。
BuildContext.read
下面两种使用方式结果是一样的
使用Provider.of<T>()
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<CountNotifier2>(context).count.toString(),
style: TextStyle(
color: Colors.red,
fontSize: 50
),
),
Padding(
padding: EdgeInsets.only(top: 20),
child: ElevatedButton(
onPressed: () => Provider.of<CountNotifier2>(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<CountNotifier2>().count.toString(),
style: TextStyle(
color: Colors.red,
fontSize: 50
),
),
Padding(
padding: EdgeInsets.only(top: 20),
child: ElevatedButton(
onPressed: () => Provider.of<CountNotifier2>(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<CountNotifier2>(
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<CountNotifier2>();
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<CountNotifier2, int>(
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<CountNotifier2>(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<CountNotifier2>(context, listen: false).increment(),
child: Text("点击加1"),
),
)
],
),
),
);
}
}
总结
Flutter
为我们提供了多种读取值的方式,上面我们对消费者四大类的一个使用和分析对比,大家可根据自己的实际应用场景去使用对应的方式。