Provider状态管理
1.为什么要使用状态管理组件
通俗的讲:当我们想在多个页面(组件/Widget)之间共享状态(数据),或者一个页面(组
件/Widget)中的多个子组件之间共享状态(数据),这个时候我们就可以用 Flutter 中的状
态管理来管理统一的状态(数据),实现不同组件直接的传值和数据共享。
通过使用小部件进行状态管理,provider
可以保证:
- 通过强制的单向数据流实现可维护性
- 可测试性/可组合性,因为始终可以模拟/覆盖值
- 健壮性,因为很难忘记处理模型/小部件的更新方案
2.如何使用Provider
1.了解原理
- ChangeNotifierProvider:创建ChangeNotifier
- ChangeNotifier:用于处理数据,类似于Android中ViewModel + LiveData, 处理完了之后notifyListeners
- Consumer:消费者,或者说监听,类似于LiveData的监听,将ChangeNotifier(ViewModel)中数据拿过来
- Widget:显示数据,等价于Android中View
provider: ^5.0.0
2.发送数据
类似于ViewModel请求数据
///发送事件,发送数据时,不需要回调监听
Provider.of<TimeCounterModel>(context,listen: false).getCurrentTime(); //发送数据,先通过
注意:
Provider.of<T>(BuildContext context, {bool listen = true}) {}
listen:表示是否监听数据,默认true监听,发送时,不需要监听
3接收数据,
第一种方式:通过Provider.of(context, listen: true).formatTime,直接监听
Widget buildBlocBuilder() {
return Container(
///外边距
margin: EdgeInsets.only(left: 12, top: 12),
child: Text(
'${Provider.of<TimeCounterModel>(context, listen: true).formatTime}',
style: TextStyle(fontSize: 22.0, color: Colors.red),
),
);
}
第二种方式:Consumer来监听
Widget buildBlocBuilder() {
return Consumer<TimeCounterModel>(
///参数 value 就是绑定的事件结果 TimeCounterModel
builder: (BuildContext context, value, Widget child) {
return Container(
///外边距
margin: EdgeInsets.only(left: 12, top: 12),
child: Text(
'${value.formatTime}',
style: TextStyle(fontSize: 22.0, color: Colors.red),
),
);
},
);
代码如下:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter/cupertino.dart';
import 'TimeModel.dart';
main() =>
//ChangeNotifierProvider
runApp(ChangeNotifierProvider(
create: (context) => TimeModel(),//创建ChangeNotifier(ViewModel)
child:ProviderMainApp(),
));
class ProviderMainApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "配制",
theme: ThemeData(
accentColor: Colors.blue,
brightness: Brightness.light,
),
///默认的首页面
home: ProviderHome(),
);
}
}
class ProviderHome extends StatefulWidget {
ProviderHome({Key key}) : super(key: key);
@override
_ProviderHomeState createState() {
return _ProviderHomeState();
}
}
class _ProviderHomeState extends State<ProviderHome> {
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
Widget buildBlocBuilder() {
return Consumer<TimeModel>( 接收数据
builder: (BuildContext context, value, Widget child) {
return Container(
margin: EdgeInsets.only(left: 12, top: 12),
child: Text(
'${value.count}',
style: TextStyle(fontSize: 22.0, color: Colors.red),
),
);
},
);
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return Column(
children: [
Container(
margin: EdgeInsets.only(top: 10, bottom: 10),
child: OutlinedButton(
child: Text("provider"),
onPressed: () {
Provider.of<TimeModel>(context, listen: false).addCount(); //发送数据
},
),
),
Container(
margin: EdgeInsets.only(top: 10, bottom: 10),
child: Text(
'${Provider
.of<TimeModel>(context, listen: true)
.count}' //接收数据
),
),
buildBlocBuilder()
],
);
}
}
TimeModel
import 'package:flutter/cupertino.dart';
class TimeModel with ChangeNotifier {
var count = 0;
void addCount() {
count++;
notifyListeners();
}
}
4.多个事情巧妙操作,类似于Android中多个ViewModel
1.通过 MultiProvider 来组合这些 ChangeNotifierProvider,也就相当于Android中注册获取ViewModel
class TestProviderMulPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
///组合多个Provider
return MultiProvider(
providers: [
///计时器
ChangeNotifierProvider(
create: (BuildContext context) {
return TimeCounterModel();
},
),
///随机数据
ChangeNotifierProvider(
create: (BuildContext context) {
return RandomNumberModel();
},
)
],
child: MaterialApp(
home: TestConsumerTimePage(),
),
);
}
}
2.消费处理,各自监听获取ChangeNotifier传递过来的数据,其他与上面一致
@override
Widget build(BuildContext context) {
///页面主体脚手架
return Scaffold(
appBar: AppBar(
title: Text("Provider "),
),
body: Column(
children: [
buildTimeConsumer(),
buildNumberConsumer(),
],
),
);
}
3.组合消费同时监听这两个结果代码
///通过 Consumer2 来同时监听处理两个结果
Widget buildTimeConsumer2() {
return Consumer2<TimeCounterModel,RandomNumberModel>(
///参数 value 为 TimeCounterModel 类型
///参数 value2 为 RandomNumberModel 类型
builder: (BuildContext context, value,value2, Widget child) {
return Container(
///外边距
margin: EdgeInsets.only(left: 12, top: 12),
child: Text(
'当前时间 ${value.formatTime} 随机数 ${value2.randomNumber}',
style: TextStyle(fontSize: 22.0, color: Colors.red),
),
);
},
);
}
4组合监听
///通过 Consumer2 来同时监听处理两个结果
Widget buildTimeConsumer2() {
return Consumer2<TimeModel02,RandomModel>(
///参数 value 为 TimeModel02 类型
///参数 value2 为 RandomModel 类型
builder: (BuildContext context, value,value2, Widget child) {
return Container(
///外边距
margin: EdgeInsets.only(left: 12, top: 12),
child: Text(
'当前时间 ${value.count} 随机数 ${value2.randomNumber}',
style: TextStyle(fontSize: 22.0, color: Colors.red),
),
);
},
);
}
多个以上监听,参照Consumer2
案例
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'RandomModel.dart';
import 'TimeModel02.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// home: CustomScrollViewDemo(),
home: Scaffold(
body: TestProviderMulPage(),
),
);
}
}
class TestProviderMulPage extends StatelessWidget {
TestProviderMulPage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
// TODO: implement build
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => TimeModel02()),
ChangeNotifierProvider(
create: (context) => RandomModel(),
)
],
child: TestConsumerTimePage(),
);
}
}
class TestConsumerTimePage extends StatefulWidget {
TestConsumerTimePage({Key key}) : super(key: key);
@override
_TestConsumerTimePageState createState() {
return _TestConsumerTimePageState();
}
}
class _TestConsumerTimePageState extends State<TestConsumerTimePage> {
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
Widget buildBlocBuilder() {
return Consumer<TimeModel02>(
接收数据
builder: (BuildContext context, value, Widget child) {
return Container(
margin: EdgeInsets.only(left: 12, top: 12),
child: Text(
'${value.count}',
style: TextStyle(fontSize: 22.0, color: Colors.red),
),
);
},
);
}
Widget buildNumberConsumer() {
return Consumer<RandomModel>(
builder: (BuildContext context, value, Widget child) {
return Container(
///外边距
margin: EdgeInsets.only(left: 12, top: 12),
child: Text(
'回传的数据 ${value.randomNumber}',
style: TextStyle(fontSize: 22.0, color: Colors.red),
),
);
},
);
}
///通过 Consumer2 来同时监听处理两个结果
Widget buildTimeConsumer2() {
return Consumer2<TimeModel02,RandomModel>(
///参数 value 为 TimeModel02 类型
///参数 value2 为 RandomModel 类型
builder: (BuildContext context, value,value2, Widget child) {
return Container(
///外边距
margin: EdgeInsets.only(left: 12, top: 12),
child: Text(
'当前时间 ${value.count} 随机数 ${value2.randomNumber}',
style: TextStyle(fontSize: 22.0, color: Colors.red),
),
);
},
);
}
@override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
SizedBox(height: 50),
Container(
margin: EdgeInsets.only(top: 10, bottom: 10),
child: OutlinedButton(
child: Text("provider"),
onPressed: () {
Provider.of<TimeModel02>(context, listen: false)
.addCount(); //发送数据
},
),
),
Container(
margin: EdgeInsets.only(top: 10, bottom: 10),
child: OutlinedButton(
child: Text("provider"),
onPressed: () {
Provider.of<RandomModel>(context, listen: false)
.testRandom(); //发送数据
},
),
),
buildBlocBuilder(), //监听1
buildNumberConsumer(),//监听2
buildTimeConsumer2()
],
));
}
}
3.selector
发现问题:Consumer来获取CounterProvider,只要是值改变了都会通知它的宿主刷新UI,简单说,ButtonA和ButtonB值的改变通过同一个Provider,只要ChangeNotifier(ViewModel)发生改变,那么ButtonA和ButtonB都会被触发,这是我们不想要的, 频繁触发,导致更多的性能消耗,用电也更多
这明显与我们使用Provider的初衷是违背的。而在实际的使用场景中,这种情况会带来比预想更差的UI表现。
解决方案:Selector来解决这类问题。
import 'package:flutter/material.dart';
class CounterProvider with ChangeNotifier {
int _count = 0;
int _count1 = 100;
int get value => _count;
int get value1 => _count1;
void increment() {
_count++;
notifyListeners();
}
void increment1() {
_count1++;
notifyListeners();
}
}
1.selector属性
Selector({
Key key,
//当父widget请求更新或者selector的返回值与之前的返回值不一样时会调用builder
@required ValueWidgetBuilder<S> builder,
//selector返回具体的值,返回的值必须继承自==而且不能为null
@required S Function(BuildContext, A) selector,
Widget child,
}) : assert(selector != null),
super(
key: key,
builder: builder,
selector: (context) => selector(context, Provider.of(context)),
child: child,
);
·Selector控制的粒度比Consumer更细,Consumer是监听一个Provider中所有数据的变化,Selector则是监听某一个/多个值的变化。具体的代码:
Selector类似于ViewModel中的LiveData
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'CounterProvider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// home: CustomScrollViewDemo(),
home: Scaffold(
body: HomePage(),
),
);
}
}
class HomePage extends StatefulWidget {
@override
State<StatefulWidget> createState() => HomePageState();
}
class HomePageState extends State<HomePage> {
///初始化CounterProvider
CounterProvider _counterProvider = new CounterProvider();
@override
Widget build(BuildContext context) {
print('页面重绘了。。。。。。。。。。。');
//整个页面使用ChangeNotifier来包裹
return ChangeNotifierProvider(
create: (context) => _counterProvider,
child:
//child里面的内容不会因为数据的改变而重绘
Scaffold(
appBar: AppBar(
title: Text('my page'),
),
body: Center(
child: Column(
children: <Widget>[
Selector(builder: (BuildContext context, int data, Widget child) {
print('Text 1 重绘了。。。。。。。。。。');
return Text(
'Text1 : ${data.toString()}',
style: TextStyle(fontSize: 20));
}, selector:
(BuildContext context, CounterProvider counterProvider) {
//这个地方返回具体的值,对应builder中的data
return counterProvider.value; //监听的counterProvider.value的值,这里发生改变时,会被监听
}),
Consumer(builder: (BuildContext context,
CounterProvider counterProvider, Widget child) {
print('Text2重绘了。。。。。。');
return Text(
//获取数据
'Text2 : ${counterProvider.value1}',
style: TextStyle(fontSize: 20),
);
}),
RaisedButton(
onPressed: () {
print('Button 1被点击了。。。。。。。。。。');
_counterProvider.increment();
},
child: Text('Button1'),
),
RaisedButton(
onPressed: () {
print('Button 2被点击了。。。。。。。。。。');
_counterProvider.increment1();
},
child: Text('Button2'),
)
],
)),
),
);
}
}
2.进一步去思考
问题1:Consumer包裹多个Selector,如果Consumer发生改变,Selector会怎么变化,如果Selector发生改变,Consumer会发生变化
结论:Selector监听的值发生变化,那么Cousumer页能监听到,所以会触发Consumer包裹的组件整体重绘。
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'CounterProvider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// home: CustomScrollViewDemo(),
home: Scaffold(
body: HomePage(),
),
);
}
}
class HomePage extends StatefulWidget {
@override
State<StatefulWidget> createState() => HomePageState();
}
class HomePageState extends State<HomePage> {
///初始化CounterProvider
CounterProvider _counterProvider = new CounterProvider();
Widget buildWidget() {
return Consumer<CounterProvider>(
builder: (BuildContext context, value, Widget child) {
return Column(
children: [
Selector(builder: (BuildContext context, int data, Widget child) {
print('Text 1 重绘了。。。。。。。。。。');
return Text('Text1 : ${data.toString()}',
style: TextStyle(fontSize: 20));
}, selector: (BuildContext context, CounterProvider counterProvider) {
//这个地方返回具体的值,对应builder中的data
return counterProvider.value;
}),
Selector(builder: (BuildContext context, int data, Widget child) {
print('Text 2 重绘了。。。。。。。。。。');
return Text('Text2 : ${data.toString()}',
style: TextStyle(fontSize: 20));
}, selector: (BuildContext context, CounterProvider counterProvider) {
//这个地方返回具体的值,对应builder中的data
return counterProvider.value1;
})
],
);
});
}
@override
Widget build(BuildContext context) {
print('页面重绘了。。。。。。。。。。。');
//整个页面使用ChangeNotifier来包裹
return ChangeNotifierProvider(
create: (context) => _counterProvider,
child:
//child里面的内容不会因为数据的改变而重绘
Scaffold(
appBar: AppBar(
title: Text('my page'),
),
body: Center(
child: Column(
children: <Widget>[
buildWidget(),
RaisedButton(
onPressed: () {
print('Button 1被点击了。。。。。。。。。。');
_counterProvider.increment();
},
child: Text('Button1'),
),
RaisedButton(
onPressed: () {
print('Button 2被点击了。。。。。。。。。。');
_counterProvider.increment1();
},
child: Text('Button2'),
)
],
)),
),
);
}
}
3.Selector再进一步思考
Selector包裹多个Selector,结果会怎么样的
结论:点击ButtonA,只绘制ButtonA,点击ButtonB,只绘制ButtonB,点击ButtonC(最外层),会触发内部所有的Selector发生重新绘制
Widget buildWiget2() {
return Selector(builder: (BuildContext context, value, Widget child) {
return Column(
children: [
Selector(builder: (BuildContext context, int data, Widget child) {
print('Text 1 重绘了。。。。。。。。。。');
return Text('Text1 : ${data.toString()} ${++_num}',
style: TextStyle(fontSize: 20));
}, selector: (BuildContext context, CounterProvider counterProvider) {
//这个地方返回具体的值,对应builder中的data
return counterProvider.value;
}),
Selector(builder: (BuildContext context, int data, Widget child) {
print('Text 2 重绘了。。。。。。。。。。');
return Text('Text2 : ${data.toString()} ${++_num}',
style: TextStyle(fontSize: 20));
}, selector: (BuildContext context, CounterProvider counterProvider) {
//这个地方返回具体的值,对应builder中的data
return counterProvider.value1;
})
],
);
}, selector: (BuildContext context, CounterProvider counterProvider) {
//这个地方返回具体的值,对应builder中的data
return counterProvider.count2;
});
}
总结:所以在使用Selector时,尽可能地颗粒度要小,Selector会将内部所欲子Widget全部刷新一遍,Consumer针对ChangeNotifier中数值地变化
参考文章:https://blog.csdn.net/u013894711/article/details/102785532