Flutter状态管理之Provider的理解使用

目录
  1. 1. 一、为什么需要Provider管理状态
  2. 2. 二、Provider的使用
    1. 2.1. 1.使用步骤
    2. 2.2. 2.最简单的例子 Provider.of(context) 方式
    3. 2.3. 3.例子 Consumer 方式
  3. 3. Provider分类
  4. 4. 使用哪种Provider?

一、为什么需要Provider管理状态

数据变化,数据共享,需要Provider

  • Flutter的代码,是响应式/声明式的。以前安卓/iOS的代码,是命令式的。
  • 响应式的代码,基本都需要进行状态管理,也可以理解为数据共享。
  • 界面、数据是变化的,就需要管理的,简单的直接在StatefulWidget进行管理就好,复杂的就是用Provider之类来管理。 简单和复杂数据的例子:
    数据的变化,怎么算简单的呢?——比如一个 PageView 组件中的当前页面、一个复杂动画中当前进度、一个 BottomNavigationBar 中当前被选中的 tab。这些在widget 树,其他部分不需要访问这种状态。不需要去序列化这种状态,这种状态也不会以复杂的方式改变。

什么数据变化需要Privider来管理呢?举例子,比如,用户选项、登录信息、一个社交应用中的通知、一个电商应用中的购物车、一个新闻应用中的文章已读/未读状态。

Flutter的状态管理有Redux、Rx、hooks、ScopedModel, 和Provider等,其中Provider是官方推荐的。
如果你不了解其他的,那肯定是官方推荐的优先。

二、Provider的使用

嗯,Provider是官方推荐的。不熟悉的,可以看看这两篇先。
Provider官方文章
使用 Provider 管理 Flutter 应用状态

1.使用步骤

引入 provider

1
2
dependencies:
provider: ^3.1.0

关于 provider 的使用可以简单理解为3步:
1.创建继承自 ChangeNotifier 的共享类
2.设置数据
3.获取数据,2种方式,分别是Provider.of(context) Consumer

很简单吧~

2.最简单的例子 Provider.of(context) 方式

以下代码:

  • 创建共享类,添加一个增长数据的方法,调用就刷新
  • 调用数据
  • build重新调用,刷新ui
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
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,演示演示,只为演示)

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
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。

参考: Flutter开始干系列-状态管理Provider3


Provider分类

Provider
最基础的Provider,它持有一个类型的值,并暴露出去,并且在任何时候都能获取到最新的值。一般情况下,下层获取上层状态,不过下层是监听不到上层状态的变化。在程序越来越复杂的情况下,使用它在多个Widget中共享状态,还是很方便的。

提示:同种类型,Provider只能维护一份数据。

ListenableProvider
监听状态改变并重绘视图。

ChangeNotifierProvider
与ListenableProvider区别在于,当需要的时候,会自动调用dispose.

ValueListenableProvider
与ListenableProvider区别在于,仅仅支持value方式获取状态。

StreamProvider
以流的方式共享数据。

FutureProvider
持有Future,并在Future完成时,通知监听者。

ProxyProvider 系列
当一个 Model 与其它的Model或者更多的Model之间存在依赖关系,即多个Model状态发生改变,都会引起这个 Model 的状态发生变化,需要使用 ProxyProvider 系列 建立多种Model之间的依赖关系。

ProxyProvider 系列包含:

  • ProxyProvider - ProxyProvider6
  • ListenableProxyProvider - ListenableProxyProvider6
  • ChangeNotifierProxyProvider - ChangeNotifierProxyProvider6
  • xxxProxyProvider
    这些ProxyProvider的使用基本上同Provider的使用,下文会着重介绍。

使用哪种Provider?

  • 需求:多个Widget共享状态,如果没有这个需求,额,怎么会没有呢,好奇怪。
  • 当不需要监听状态改变,下层仅仅是获取上层状态,使用Provider.
  • 需要共享数据,并监听数据发生变化,使用ListenableProvider或者ChangeNotifierProvider,如果自主管理数据的dispose,使用ListenableProvider.
  • 如果两种类型数据,或者多种数据存在依赖关系,使用ProxyProvider 系列。
  • 最方便的使用,也是懒人最强方式,使用ChangeNotifierProviderChangeNotifierProxyProvider.
  • 技术正在不断的完善,官方更新比较频繁,需要时时关注动态。

转自:https://github.com/smiling1990/FlutterProvider