前言
我们通过一个实际页面来使用并且理解一下Flutter Provider
的使用,了解下Provider
如果进行状态存储以及共享
页面
微信图片_20220604131605.jpg | 微信图片_20220604131740.jpg |
InheritedWidget
InheritedWidget
是一个功能性的组件,可以在组件树种从上往下的进行数据共享,定义在InheritedWidget
组件中的数据可以被其子节点获取到,并且当InheritedWidget
刷新时,所以依赖它的子节点都会进行刷新
创建一个需要共享的数据类
class ShareData{
bool isEnableBiometric;
void setIsEnableBiometric(bool isEnableBiometric){
this.isEnableBiometric = isEnableBiometric;
}
ShareData(this.isEnableBiometric);
}
创建一个InheritedWidget
里面包含需要被共享的数据
class ShareWidget extends InheritedWidget {
ShareData shareData;
static ShareWidget? of(BuildContext context) {
//会将调用该方法的Widget进行注册,当数据刷新时咋会对其进行刷新,所注册组件的会调用didChangeDependencies -> build
return context.dependOnInheritedWidgetOfExactType<ShareWidget>();
}
static ShareData? ofValue(BuildContext context) {
//会将调用该方法的Widget不会进行注册
return context.findAncestorWidgetOfExactType<ShareWidget>()?.shareData;
}
ShareWidget({required this.shareData, Key? key, required Widget child})
: super(key: key, child: child);
@override
bool updateShouldNotify(covariant InheritedWidget oldWidget) {
///框架是否通知继承于这个组件并注册的子组件
return true;
}
}
static ShareWidget? of(BuildContext context)
由于子节点需要使用共享的数据,所以需要暴露出函数让子节点可以拿到父或者祖节点的InheritedWidget
从而拿到共享数据,并且在调用findAncestorWidgetOfExactType
时子Widget
会进行注册,从而在日后InheritedWidget
变化时可以被刷新,当组件树中的InheritedWidget
更新时会通知所有已经注册的子组件进行刷新状态
updateShouldNotify
表示已经注册的子Widget
是否会在InheritedWidget
变化时刷新,当子Widget
被通知刷新时会调用Widget
的didChangeDependencies
函数
在主页面定义初始化共享数据
在主页面使用InheritedWidget
class TestSharePageState extends State {
ShareData shareData = ShareData(false);
@override
void initState() {
super.initState();
Future.delayed(Duration(seconds: 5),(){
setState(() {
shareData.setIsEnableBiometric(true);
});
});
}
@override
Widget build(BuildContext context) {
return ShareWidget(shareData: shareData,
child: Column(
children: [
TestSharePage1(),
TestSharePage2(),
TestSharePage3(),
],
));
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
print('TestSharePageState didChangeDependencies');
}
}
在子页面使用共享数据
class TestSharePage1State extends State {
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
child: Text(
'TestSharePage1${ShareWidget.of(context)?.shareData.isEnableBiometric ?? null}'),
);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
print('TestSharePage1State didChangeDependencies');
}
}
class TestSharePage2State extends State {
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
child: Text(
'TestSharePage2${ShareWidget.of(context)?.shareData.isEnableBiometric ?? null}'),
);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
print('TestSharePage2State didChangeDependencies');
}
}
class TestSharePage3State extends State {
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
child: Text('TestSharePage3 test'),
);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
print('TestSharePage3State didChangeDependencies');
}
}
刷新共享数据
主界面在5秒之后刷新了共享数据
@override
void initState() {
super.initState();
Future.delayed(Duration(seconds: 5),(){
setState(() {
shareData.setIsEnableBiometric(true);
});
});
}
五秒之后会打印,并且Page1
和Page2
都会刷新,Page3
由于没有注册所以不会刷新
I/flutter ( 3479): TestSharePage1State didChangeDependencies
I/flutter ( 3479): TestSharePage2State didChangeDependencies
只使用数据而不注册
上述测试中,Page3
由于没有使用共享数据,所以共享数据在刷新的时候Page3
没有注册所以没有刷新,我们也可以只使用Page3
但是不注册这样就不会刷新了
使用下列方式可以只是使用而不进行注册
static ShareData? ofValue(BuildContext context) {
//会将调用该方法的Widget不会进行注册
return (context.getElementForInheritedWidgetOfExactType<ShareWidget>()?.widget as ShareWidget).shareData;
}
class TestSharePage3State extends State {
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
child: Text('TestSharePage3 ${ShareWidget.ofValue(context)?.isEnableBiometric ?? 'null'}}'),
);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
print('TestSharePage3State didChangeDependencies');
}
}
此时进行数据刷新,则Page3不会调用didChangeDependencies
,但是数据依旧刷新了,因为父节点在重新构建时,子节点也会刷新
Provider
上面我们基本了解了InheritedWidget
是因为父子节点的关系所以可以子啊父节点中存储数据,子节点中使用数据从而进行数据共享以及局部页面刷新等操作,Provider框架也是基于这样的原理去构建的一个更好用的全局状态管理,数据共享,局部刷新框架
我们来使用Provider框架去实现我们最开偷那两张图片的案例
首先我们添加provider
依赖
provider: ^4.0.4
简单分析一下案例
案例中我们需要选择多个兴趣标签,并且会显示你已经选择了几个标签,然后进行提交,那么我们所共享的数据就是用户所选择的兴趣标签,并且共享数据的范围就是当前页面
共享数据的提供: 只是在当前页面的顶部对共享数据进行提供以及初始化,默认选中的兴趣标签数组数量为0
需要使用到共享数据的组件:
1.每个兴趣的item
需要使用到共享数据用以判断当前item是否需要被选中(变色)
2.已选择数量所展示的Text
3.底部的提交按钮需要用到共享数据,但是只是使用而已
Provider的介绍
它是对InheritedWidget
的一个分封装以及扩展,提供了更好的性能以及更简单的使用方式等
创建需要共享的数据类
class Topics {
var _chooseTopics = <String>[];
List<String> get chooseTopics => _chooseTopics;
void chooseTopic(String topic) {
if (!_chooseTopics.contains(topic)) {
_chooseTopics.add(topic);
} else {
_chooseTopics.remove(topic);
}
}
}
将共享数据通过Provider暴露
class ChooseTopicPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return ChooseTopicState();
}
}
class ChooseTopicState extends State {
Topics _topics = Topics();
@override
Widget build(BuildContext context) {
print('ChooseTopicState build');
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('Topics', style: TextStyle(color: Colors.black)),
backgroundColor: Colors.white,
leading: Icon(Icons.arrow_back_ios, color: Colors.green)),
body: Provider<Topics>(
create: (_) => _topics,
child: Container(
padding: EdgeInsets.all(20),
child: Column(children: [
TopicWrap(),
Container(
height: 40,
decoration: BoxDecoration(
borderRadius: BorderRadius.all((Radius.circular(5))),
color: Colors.green),
child: MaterialButton(
textColor: Colors.white,
onPressed: () => {
},
child: Text('选择'),
),
),
]),
),
));
}
}
这里的TopicWrap
是一个封装的Widget
,在里面会使用到共享数据
class TopicWrapState extends State {
List<String> topics = [
"欧美电影",
"日本电影",
"日本动漫",
"大陆电影",
"恐怖电影",
"豆瓣Top250",
];
List<Widget> getWrapList() {
var childrens = <Widget>[];
for (var i = 0; i < topics.length; i++) {
childrens.add(GestureDetector(
child: Container(
padding: EdgeInsets.all(5),
margin: EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(5)),
color: Provider
.of<Topics>(context).chooseTopics.contains(topics[i])
? Colors.orange
: Colors.green),
child: Text(
topics[i],
style: TextStyle(color: Colors.white),
),
),
onTap: () {
Provider.of<Topics>(context,listen: false).chooseTopic(topics[i]);
setState(() {
});
}));
}
return childrens;
}
@override
Widget build(BuildContext context) {
return Column(children: [
Wrap(children: getWrapList()),
Container(
margin: EdgeInsets.all(20),
child: Text('已选择${Provider
.of<Topics>(context)
.chooseTopics
.length}'),
),
],);
}
}
这里使用Provider.of
获取到了共享数据的长度
Provider.of<Topics>(context).chooseTopics.length
并且在点击条目的时候也是使用同样的方式获取共享数据然后修改数据,这是使用listen: false
是因为这里并没有组件使用并显示数据,所以并不需要对其进行注册
Provider.of<Topics>(context,listen: false).chooseTopic(topics[i])
然后在修改数据之后使用了setState(() { })
进行页面刷新,然后就可以达到更新的效果
所以我们的结构是:
图片1.png这样可以实现功能,Provider
只是提供了数据存储的功能,并且每次数据变化都个要刷新整个Wrap
页面从而达到对应的效果
ChangeNotifyProvider
使用ChangeNotifyProvider
,它会在数据更新之后通知所有注册的Widget去进行build,不用我们手动的去更新页面
修改数据类
ChangeProvider
需要一个ChangeNotifier
类型的共享数据,并且需要在数据被操作需要刷新时调用notifyListeners()
函数
class Topics extends ChangeNotifier {
var _chooseTopics = <String>[];
List<String> get chooseTopics => _chooseTopics;
void chooseTopic(String topic) {
if (!_chooseTopics.contains(topic)) {
_chooseTopics.add(topic);
} else {
_chooseTopics.remove(topic);
}
notifyListeners();
}
}
将Provider
修改为ChangeNotifyProvider
然后移除掉上述案例中的setState(() { })
,还是可以达到刚才的效果
Consumer
在我们的案例中底部还有一个选择的确认按钮,他需要拿到共享数据然后做一些其他的业务操作,我们给他加上获取数据的代码
@override
Widget build(BuildContext context) {
print('ChooseTopicState build');
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('Topics', style: TextStyle(color: Colors.black)),
backgroundColor: Colors.white,
leading: Icon(Icons.arrow_back_ios, color: Colors.green)),
body: ChangeNotifierProvider<Topics>(
create: (_) => _topics,
child: Container(
padding: EdgeInsets.all(20),
child: Column(children: [
TopicWrap(),
Container(
height: 40,
decoration: BoxDecoration(
borderRadius: BorderRadius.all((Radius.circular(5))),
color: Colors.green),
child: MaterialButton(
textColor: Colors.white,
onPressed: () => {
///新增代码
print('选择完毕-${ Provider.of<Topics>(context).chooseTopics}')
},
child: Text('选择'),
),
),
]),
),
));
}
当我们点击按钮时会报错
Error: Could not find the correct Provider<Topics> above this ChooseTopicPage Widget
This happens because you used a `BuildContext` that does not include the provider
of your choice. There are a few common scenarios:
为什么同为Child
的TopicWrap
不会有这个异常?
这是因为
Provider
是根据InheritedWidget
去实现的,是根据父子关系视图树进行共享书嫉妒而实现
TopicWrap
是由一个新的BuildContext去创建的,他已经拥有了所有Parent节点包括Provider,所以它可以获取到Provider以及它里面的共享数据,而MaterialButton
是和Provider在一个BuildContext下,他们是在同一个BuildContext下创建以及初始化,MaterialButton
并非Provider的后代控件
解决: 我们可以使用Provider
的build
属性他返回一个新的Context
供我们使用,基于新的context
我们是可以拿到Provider
的
body: ChangeNotifierProvider<Topics>(
create: (_) => _topics,
builder: (context,child){
return Container(
padding: EdgeInsets.all(20),
child: Column(children: [
TopicWrap(),
Container(
height: 40,
decoration: BoxDecoration(
borderRadius: BorderRadius.all((Radius.circular(5))),
color: Colors.green),
child: MaterialButton(
textColor: Colors.white,
onPressed: () => {
print('选择完毕-${ Provider.of<Topics>(context,listen: false).chooseTopics}')
},
child: Text('选择'),
),
),
]),
);
},
)
我们也可以使用Consumer去优化我们的Provider
处理,让我们只是刷新数据变化的部分不用调用build
函数,而且也不需要在顶层去声明Provider
嵌套复杂
- 它不需要在在顶层预先申明一个Provider
- 并且只是更新数据变化的局部,而不是重新调用整个
buld
函数
使用Consumer包裹你使用到共享数据的控件
使用builder
属性可以拿到新的context
以及共享数据,然后当共享数据发生变化时,你也会进行此控件的局部刷新并不会调用当前的build
做到局部刷新,也不用考虑控件的层级
ChangeNotifierProvider<Topics>(
create: (_) => _topics,
child: Container(
padding: EdgeInsets.all(20),
child: Column(children: [
TopicWrap(),
Consumer<Topics>(builder: (context, topics, child) {
return Text('已选择${topics.chooseTopics}');
}),
Container(
height: 40,
decoration: BoxDecoration(
borderRadius: BorderRadius.all((Radius.circular(5))),
color: Colors.green),
child: MaterialButton(
textColor: Colors.white,
onPressed: () => {
print(
'选择完毕-${Provider.of<Topics>(context, listen: false).chooseTopics}')
},
child: Text('选择'),
),
),
]),
),
)
微信图片_20220605161659.jpg
欢迎关注Mike的简书
Android 知识整理
网友评论