状态管理
在 Flutter 开发中,状态管理是一个永恒的话题。一般的原则是:如果状态是组件私有的,则应该由组件自己管理;如果状态要跨组件共享,则该状态应该由各个组件共同的父元素来管理。对于组件私有的状态管理很好理解,但对于跨组件共享的状态,管理的方式就比较多了,如使用全局事件总线EventBus,它是一个观察者模式的实现,通过它就可以实现跨组件状态同步:状态持有方(发布者)负责更新、发布状态,状态使用方(观察者)监听状态改变事件来执行一些操作。(转自:Flutter实战.第二版 7.3章节)
provider
provider是flutter官方提供的状态管理widget。使用provider客户管理的数据,可以提供给子孙节点使用。
举例
1、引入库
...
# 状态管理
provider: ^6.0.1
...
2、创建数据model
数据源:
- 一个CountModel类,混合ChangeNotifier。
- 一个count变量,用于计数,
- 一个increase方法调用一次则count+1,并调用notify方法
import 'package:flutter/cupertino.dart';
class CountModel with ChangeNotifier{
int _count=0;
int get count => _count;
//修改数据,并调用notify方法
void increase(){
_count++;
//修改数据后调用notify方法
notifyListeners();
}
}
3、创建全局共享数据
ChangeNotifierProvider.value 可以提供数据供子孙节点使用,并且可以在数据改变的时候通知所有子节点刷新。
- 创建数据countModel
- 创建ChangeNotifierProvider对象,把countModel传给value,让其子节点都可以访问到。并把MyApp传给child
void main() {
//防止报错
WidgetsFlutterBinding.ensureInitialized();
Provider.debugCheckInvalidValueType = null;
//全局数据
var countModel = CountModel();
var textSize = 12;
//只要是当前provider下的子节点都可以使用countModel变量
var provider = ChangeNotifierProvider<CountModel>.value(
value: countModel,
child: MyApp(),
);
runApp(provider);
}
4、子节点处理数据
- Provider.of获取到顶层数据
- 点击按钮会调用countModel.increase()方法,里面又调用notifyListeners()方法
- notifyListeners()通过provider会调用build方法即去更新UI。
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
var countModel = Provider.of<CountModel>(context);
print('countModel:${countModel.hashCode}');
print('count:${countModel.count}');
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("状态管理")),
body: Column(
children: [
Text("计数${countModel.count}"),
RaisedButton(
onPressed: () {
countModel.increase();
},
child: Text("增加"),
)
],
),
),
);
}
}
log输出
Performing hot reload...
Syncing files to device Android SDK built for x86...
I/flutter ( 811): countModel:895566778
I/flutter ( 811): count:3
Reloaded 0 libraries in 955ms.
I/flutter ( 811): countModel:895566778
I/flutter ( 811): count:4
界面就不演示了
扩展
多个子节点
新增一个FirstPage页面
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var countModel = Provider.of<CountModel>(context);
// var textSize = Provider.of<int>(context);
return Scaffold(
backgroundColor: Colors.amber,
appBar: AppBar(
title: Text("firstPage"),
),
body: Center(
child: Text("count:${countModel.count}"),
),
);
}
}
MyApp
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
var countModel = Provider.of<CountModel>(context);
print('countModel:${countModel.hashCode}');
print('count:${countModel.count}');
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("状态管理")),
body: Column(
children: [
Text("计数${countModel.count}"),
RaisedButton(
onPressed: () {
countModel.increase();
},
child: Text("增加"),
),
Expanded(child: FirstPage())//添加firstPage
],
),
),
);
}
}
当点击增加按钮,myapp页面和firstpage页面的数据同时更新
效果
使用Consumer
Consumer 使用了 Builder 模式,收到更新通知就会通过 builder 重新构建。Consumer 代表了它要获取哪一个祖先中的 Model。
Consumer 的 builder 实际上就是一个 Function,它接收三个参数 (BuildContext context, T model, Widget child)。
- context: context 就是 build 方法传进来的 BuildContext
- T:T也很简单,就是获取到的最近一个祖先节点中的数据模型。
- child:它用来构建那些与 Model 无关的部分,在多次运行 builder 中,child 不会进行重建。
然后它会返回一个通过这三个参数映射的 Widget 用于构建自身。
在这个浮动按钮的例子中,我们通过 Consumer 获取到了顶层的 CounterModel 实例。并在浮动按钮 RaisedButton 的 onPressed 中调用其 increase 方法。
而且抽离出Consumer中不变的部分,也就是控件Text(“不变的widget222”,style: TextStyle(color: Colors.red)其作为 child 参数传入 builder 方法中。
//第二页
class TwoPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('TwoPage Page'),
),
body: Consumer<CountModel>(
builder: (context, CountModel counter, child) => Center(
child: Text(
'可变的widget,计数: ${counter.count}',//重新定义了一个新widget修改数据
),
),
child: Text('不变的widget'),
),
floatingActionButton: Consumer<CountModel>(
builder: (context, CountModel counter, child) => RaisedButton(
onPressed: () {
counter.increase();
},
child: child,//这里传过去的是控件:不变的widget222
),
child: Text(
"不变的widget222",
style: TextStyle(color: Colors.red),
),
),
);
}
}
跳转至第二页
//第一页
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var countModel = Provider.of<CountModel>(context);
// var textSize = Provider.of<int>(context);
return Scaffold(
backgroundColor: Colors.amber,
appBar: AppBar(
title: Text("firstPage"),
),
body: Center(
child: Text("count:${countModel.count}"),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => TwoPage()));
},
child: Icon(Icons.navigate_next),
),
);
}
}
效果