provider状态管理

几种常见情况,点击一个按钮,改变另外一个控件的值
思路: 首先给要改变值得控件绑定一个监听,${context.watch().count},如果点击按钮改变了数据,监听文字自动发生改变。

点击一个按钮,获取另外一个控件的值
思路:首先这个控件上显示的数据肯定不是死数据, 否则也没有获取的意义了,如果你说默认是死数据但是有可能会发生改变, 那么发生改变的时候应该用一个变量来控制。也就是先用${context.watch().count}监听, 死数据也通过改变数据的方式来改变控件文字。这样获取的时候就用${context.read().count}即可。

初始化数据后, 通过数据改变更改控件数据
思路:和第二种情况类似, 通过model来驱动视图。

看下 ChangeNotifierProvider 用法,一个单一的观察者模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//继承ChangeNotifier后,可以通知所有订阅者
class Counter with ChangeNotifier {
int _count = 0; //要保存的数据
int get count => _count; //提供全局get方法获取计数总值
void increment() {//提供全局方法,让全局计数+1
_count++;
notifyListeners(); //当数值改变后,通知所有订阅者刷新ui
}
}

runApp(
/// Providers are above [MyApp] instead of inside it, so that tests
/// can use [MyApp] while mocking the providers
// MultiProvider(
// providers: [
// ChangeNotifierProvider(create: (_) => Counter()),
// ],
// child: MyApp(),
// ),
ChangeNotifierProvider( //页面只需要一个provider情况
create: (_) => Counter(),
child: MyApp(),
),
);

需要监听修改的Text

1
${context.watch<Counter>().count}

读取最新值

1
${context.read<Counter>().count}

点击触发count

1
context.read<Counter>().increment(),

provider入门实战
本人使用的 provider: ^4.3.2+3,最新为5.0.0,大家可以按需配置依赖

下边开始用provider实现计数器

首先创建model类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';

//继承ChangeNotifier后,可以通知所有订阅者
class CounterModel with ChangeNotifier {
int _count;//要保存的数据,我这里实现计数器,所以只有一个int变量
CounterModel(this._count);

void add() {//提供全局方法,让全局计数+1
_count++;
notifyListeners(); //当数值改变后,通知所有订阅者刷新ui
}

get count => _count; //提供全局get方法获取计数总值
}

修改官方的计数器代码
修改Flutter初始项目main.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_demo/provider/Counter.dart';
import 'package:provider_demo/widgets/menu.dart';

void main() {
runApp(
ChangeNotifierProvider(//全局状态设置
create: (context) => CounterModel(0),//创建一个countermodel全局状态类,管理count值
child: MyApp(),
),
);
}

//不多做介绍
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Provider'),
);
}
}

class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;

@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: [
IconButton(
icon: Icon(Icons.golf_course),
onPressed: () {//创建一个跳转界面,跳转到新的路由,本跳转不传任何值
Navigator.push(context, MaterialPageRoute(builder: (context) {
return MenuView();
}));
},
),
],
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
//以下代码Provider.of<model类名>(context).属性值
//请注意,属性值在model类中必须有get方法
"${Provider.of<CounterModel>(context).count}",
style: Theme.of(context).textTheme.headline4,//字体样式
),
],
),
),
floatingActionButton: FloatingActionButton(//创建一个悬浮按钮
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}

void _incrementCounter() {//悬浮按钮点击事件
//context.read<model类名>().model中的方法;
context.read<CounterModel>().add();
}
}

一个简单的全局共享就完成了

注:不建议在程序入口初始化Provider,这里只是为了演示方便这么做,实际项目中要是都在程序入口初始化可能会导致内存急剧增加,除非是共享一些全局的状态,例如app日夜间模式切换,中英文切换等

这时候我们发现,我们没有传值更没有返回值,就能轻松两个界面管理一个数据,是不是效率高了很多

但是我们发现,只能全局管理一个状态,那么怎么管理多个状态呢?

很简单,在main.dart

1
2
3
4
5
6
7
8
void main() {
runApp(
ChangeNotifierProvider(//全局状态设置
create: (context) => CounterModel(0),//创建一个countermodel全局状态类,管理count值
child: MyApp(),
),
);
}

改为

1
2
3
4
5
6
7
8
9
10
11
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => CounterModel(0)),
//可以继续添加,语法如上,这样可以全局管理多个状态
],
child: MyApp(),
),
);
}

总结
通过简单demo我们使用了provider演示计数器,那么在实际开发中,不会只管理这么简单的数据,如果管理数据过多,provider就给我们节省了大量工作量

一个model类可以有多个属性,一个app可以有多个model类
全局管理类,不见得用model结尾,但是我个人喜欢用model来存储数据
model类必须要继承ChangeNotifier类,否则无法刷新数据
model管理的状态,只有get方法,修改他的值是通过单独的方法进行修改的,在修改后要调用notifyListeners方法

  • 触发者(Provider.of):如果只是需要获取到数据model,不需要监听变化(例如点击按钮),推荐使用Provider.of(context, listen: false)来获取数据model。

  • 监听者(推荐使用Consumer):推荐使用Consumer。

  • 简化了监听回调,实际是内部自动注册监听了;

  • 注意局部使用 Provider;并不是所有数据都要放在 main 里面,放到使用到数据的顶层就行了;

我们在代码中是采用Provider.of来获取CounterProvider,这中获取方式确实会引起整个页面的重绘,至于原因不在本章讨论范围,以后有时间再说。 那么Provider到底能不能实现“局部刷新”呢? 当然是可以的,不然这个框架真的没啥用了。下面我们再来认识一位重量级嘉宾:

Consumer

这一节我们沿用计数器的代码,对其进行改造。之前我们提到了在程序入口初始化Provider是很不规范的,所以我们改成在页面级别初始化,并结合Consumer来使用。

provider状态管理

使用Provider.of获取值的时候不要在initStates里面去取,需要在build函数或者 didChangeDependencies函数中去取.

不要在只会调用一次的组件生命周期中调用Provider,比如如下的使用方法是错误的

1
2
3
4
initState() {
super.initState();
print(Provider.of<Foo>(context).value);
}

要解决这个问题,要么使用其他生命周期方法(didChangeDependencies/build)

1
2
3
4
5
6
7
8
didChangeDependencies() {
super.didChangeDependencies();
final value = Provider.of<Foo>(context).value;
if (value != this.value) {
this.value = value;
print(value);
}
}

或者指明你不在意这个值的更新,比如

1
2
3
4
initState() {
super.initState();
print(Provider.of<Foo>(context, listen: false).value);
}

以上已经完成单个页面状态的管理,如果你想实现跨组件,跨路由状态共享。你只要把ChangeNotifierProvider放在整个应用的Widget树的根上即可。

Flutter状态管理:Provider4 入门教程(一)
【Flutter 技能篇】你不得不会的状态管理 Provider
[- Flutter-技能篇 -] 使用Provider前你应了解Consumer
Flutter Provider状态管理-Consumer
Flutter | 状态管理指南篇——Provider