Flutter状态管理之Provider的理解使用
一、为什么需要Provider管理状态
数据变化,数据共享,需要Provider
- Flutter的代码,是
响应式/声明式
的。以前安卓/iOS的代码,是命令式
的。 响应式
的代码,基本都需要进行状态管理
,也可以理解为数据共享
。界面、数据是变化的,就需要管理的,
简单的直接在StatefulWidget进行管理就好,复杂的就是用Provider之类来管理
。
简单和复杂数据的例子
数据的变化,怎么算简单的呢?——比如一个 PageView 组件中的当前页面、一个复杂动画中当前进度、一个 BottomNavigationBar 中当前被选中的 tab。这些在widget 树,其他部分不需要访问这种状态。不需要去序列化这种状态,这种状态也不会以复杂的方式改变。
什么数据变化需要Privider来管理呢?
举例子,比如,用户选项、登录信息、一个社交应用中的通知、一个电商应用中的购物车、一个新闻应用中的文章已读/未读状态
。
.
.
Flutter的状态管理有Redux
、Rx
、hooks
、ScopedModel
, 和Provider
等,其中Provider
是官方推荐的。
如果你不了解其他的,那肯定是官方推荐的优先。
二、Provider的使用
嗯,Provider是官方推荐的。不熟悉的,可以看看这两篇先。
二.1 使用步骤
引入 provider
dependencies:
provider: ^3.1.0
复制代码
关于 provider 的使用可以简单理解为3步:
- 创建继承自 ChangeNotifier 的共享类
- 设置数据
- 获取数据,2种方式,分别是Provider.of(context) 和 Consumer
很简单吧~
.
.
.
二.2 最简单的例子 Provider.of(context) 方式
以下代码:
- 创建共享类,添加一个增长数据的方法,调用就刷新
- 调用数据
- build重新调用,刷新ui
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter/foundation.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 创建 Widget 持有 CounterNotifier 数据
return ChangeNotifierProvider.value(
value: CounterNotifier(),
child: MaterialApp(
title: 'Privoder Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ProvidePage(title: 'Provider 测试页面'),
),
);
}
}
class ProvidePage extends StatelessWidget {
final String title;
ProvidePage({Key key, this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
// 获取 CounterNotifier 数据 (最简单的方式)
final counter = Provider.of<CounterNotifier>(context);
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'按下按钮,使数字增长:',
),
Text(
'${counter.count}',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
counter.increment();
},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
// 核心:继承自ChangeNotifier
// 这种文件本来应该单独放在一个类文件连的
class CounterNotifier with ChangeNotifier {
int _count = 0;
int get count => _count;
increment() {
_count++;
// 核心方法,通知刷新UI,调用build方法
notifyListeners();
}
}
复制代码
.
.
效果:
注意
- 使用 Provider.of 当ChangeNotifier 中调用 notifyListeners 时每次会重新调用 Widget 中的 build
大概如此了。你可能谁说,直接 setState 就好了。不,这肯定不一样,setState太不灵活了。
.
.
.
二.3 例子 Consumer 方式
例子:在页面一设置1个值,然后在页面2显示出来 (不要问为什么不直接用Navigator,演示演示,只为演示)
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
main() {
runApp(ChangeNotifierProvider<CounterNotifier>.value(
value: CounterNotifier(),
child: MyApp(),
));
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return MultiProvider(
providers: [
Provider.value(value: 36),
ChangeNotifierProvider.value(value: CounterNotifier())
],
child: MaterialApp(
title: 'Privoder Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Page1(),
),
);
}
}
class Page1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
//获取文字大小
final size = Provider.of<int>(context).toDouble();
// 获取计数
final counter = Provider.of<CounterNotifier>(context);
// 调用 build 时输出
print('rebuild page 1');
return Scaffold(
appBar: AppBar(
title: Text('Page1'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// 显示计数
Text(
'Current count: ${counter.count}',
// 设置文字大小
style: TextStyle(
fontSize: size,
),
),
SizedBox(
height: 50,
),
// 跳转 Page2
RaisedButton(
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(builder: (context) => Page2()),
),
child: Text('Next'),
),
],
),
),
);
}
}
class Page2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('rebuild page 2');
return Scaffold(
appBar: AppBar(
title: Text('Page2'),
),
body: Center(
child: Consumer2<CounterNotifier, int>(
builder: (context, counter, size, _) {
print('rebuild page 2 refresh count');
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'${counter.count}',
style: TextStyle(
fontSize: size.toDouble(),
),
),
],
);
}),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// 不需要监听改变(listen: false 不会重新调用build)
Provider.of<CounterNotifier>(context, listen: false).increment();
},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
class CounterNotifier with ChangeNotifier {
int _count = 0;
int get count => _count;
increment() {
_count++;
notifyListeners();
}
}
复制代码
.
注意
- 1、在Page2中,使用了Consumer
- 2、Provider.of的listen如果为false,不会重新调用build
.
页面2设置数值,返回页面1时,页面1显示的是页面2的数值
.
.
.
对比
- 触发者(Provider.of):如果只是需要获取到数据model,
不需要监听变化
(例如点击按钮),推荐使用Provider.of(context, listen: false)来获取数据model。 - 监听者(推荐使用Consumer):推荐使用Consumer。
作者:夏天舒服
链接:https://juejin.im/post/5d77bdf2518825513b537958
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
THE END