0%

Flutter状态管理Riverpod、Provider、GetX

刚接触Flutter开发时,不管是为了性能还是项目代码简洁等原因,总归还是会选择一个合适的状态管理工具。但是看着茫茫多的选择还是感觉无从下手,当然我也一样。下方记录一下各种状态管理(Riverpod、Provider、Getx)的使用,以便后续遇到其他项目使用不同状态管理时能快速上手。也希望为看到这篇文章的同学提供一些参考。

Riverpod

使用的前提需要注意,需要使用ProviderScope包裹父级节点。

Provider

Provider可以简单的用来共享数据,也可以用来拆分逻辑用来监听一些值的变化。

1
2
3
4
5
6
7
8
///简单共享数据
final _counterProvider = Provider<int>((ref) => 1);

/// 使用的地方
Consumer(builder: (context, ref, child) {
return Text(ref.read(_counterProvider).toString());
},
)

用来拆分逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class _UserInfo {
String phone;
String pwd;
_UserInfo({
this.phone = '',
this.pwd = '',
});
}

final _userInfoProvider = StateNotifierProvider<_UserInfoNotifier, _UserInfo>(
(ref) => _UserInfoNotifier());

final _isComplete = Provider<bool>((ref) {
final info = ref.watch(_userInfoProvider);
return info.phone.length == 11 && info.pwd.length >= 8;
});

Consumer(builder: (context, ref, _) {
final isComplete = ref.watch(_isComplete);
return TextButton(
onPressed: isComplete ? () {} : null,
child: const Text('登录'));
})

StateProvider

对于简单数据的状态管理,可以直接使用StateProvider,相比于Provider提供了直接修改数据的能力,相比于StateNotifierProvider,避免了为实现简单逻辑还要编写一个StateNotifier类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
final _counter = StateProvider((ref) => 0);

/// 改变状态值
ref.read(_counter.state).state += 1;
/// 重新设置为默认值
ref.refresh(_counter);
/// 监听值的改变
ref.listen(_counter, (_, value) {
print(value);
});
Consumer(builder: (context, ref, _) {
return Text(ref.watch(_counter).toString());
})

StateNotifierProvider

一般页面都会有比较复杂的逻辑,这个时候就推荐创建 CustomStateCustomStateNotifier\, 以及提供StateNotifierProvider 下方代码可能太多,具体例子可以在此处查看

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
mixin CodeLoginProviders {
final manager =
StateNotifierProvider.autoDispose<CodeLoginStateNotifier, CodeLoginState>(
(ref) {
return CodeLoginStateNotifier();
});

late final canSendCode = Provider.autoDispose<bool>((ref) {
final state = ref.watch(manager);
return state.phone.length == 11;
});

...
}

class CodeLoginStateNotifier extends StateNotifier<CodeLoginState> {
CodeLoginStateNotifier()
: super(
CodeLoginState(phone: DeerStorage.phone),
);
Timer? _timer;

...

void sendCode() {
_startTimer();
}

void _startTimer() {
_stopTimer();
_hadSendCode = true;

_timeCount = 60;
_timer = Timer.periodic(const Duration(seconds: 1), (_) {
if (state.timeCount == 1) {
_stopTimer();
} else {
_timeCount = state.timeCount - 1;
}
});
}

...
}

class CodeLoginState extends Equatable {
final String phone;
final String code;
final int timeCount;
final bool hadSendCode;

...
}
复制代码


FutureProvider

FutureProvider可以用来进行异步操作,比如网络请求、应用启动加载配置等

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
/// 网络请求
final request = FutureProvider.autoDispose<int>((ref) async {
ref.onDispose(() {
/// 可以在此处取消网络请求
print('dispose');
});
await Future.delayed(const Duration(seconds: 2));
return Random().nextInt(1000);
});

Consumer(builder: (context, ref, _) {
return ref.watch(request).map(data: (value) {
if (value.isLoading) return const CircularProgressIndicator();
return Text('数据 ${value.value}');
}, error: (error) {
return Text(error.error.toString());
}, loading: (_) {
return const CircularProgressIndicator();
});
})


/// 模拟App启动时加载必要的配置
final sharePreference = FutureProvider<void>((ref) async {
/// 可以保存起来,封装存储 ,或者直接返回,使用此provider
await SharedPreferences.getInstance();
});

final loadConfig = FutureProvider<void>((ref) async {
/// 获取一些本地存储的数据等
await Future.delayed(const Duration(seconds: 2));
});

/// 其他配置
final otherFuture = FutureProvider<void>((ref) async {
await Future.delayed(const Duration(seconds: 4));
});

/// 统一配置状态完成provider
final appConfig = FutureProvider((ref) async {
await Future.wait([
ref.watch(sharePreference.future),
ref.watch(loadConfig.future),
ref.watch(otherFuture.future),
]);
});

// 可以在应用启动时包裹,提供 Splash页面。
Consumer(builder: (context, ref, _) {
return ref.watch(appConfig).when(data: (_) {
print(DateTime.now());
return Text('程序初始化完成');
}, error: (_, __) {
return Text('错误');
}, loading: () {
print(DateTime.now());
return Text('loading');
});
})


family & dependencies

像商品详情页,如果你的provider定义的是全局变量,可以使用family使用商品的id作为入参,提供不同的StateNotifier。我一般是使用mixin提供页面相关的provider,State混入即可,感觉这样更适合单页面的状态管理。

1
2
3
4
5
6
7
8
9
10
 /// pageView切换
static final pageIndex = StateProvider<int>((ref) => 0);
static final isSelected = Provider.family<bool, int>((ref, arg) {
final index = ref.watch(pageIndex);
return index == arg;
}, dependencies: [pageIndex]);

/// 对应下标的item是否处于点击状态
final isSelected = ref.watch(HeaderProviders.isSelected(index));

overrideWithValue

可以设置provider的值 如例子中应用初始化配置完成后,为全局Provider设置当前用户的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
/// 等待本地存储等加载完成后设置用户信息
final userInfo =
StateNotifierProvider<DeerUserInfoState, DeerUserInfo?>(
(ref) => throw UnimplementedError())

ProviderScope(
overrides: [
UserProviders.userInfo
.overrideWithValue(DeerUserInfoState(DeerStorage.userInfo)),
],
...
)

Provider

简单的提供数据共享,不会刷新UI,使用和InheritedWidget差不多,树

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
class ProviderPage extends StatelessWidget {
const ProviderPage({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(ProviderRouters.counter),
),
body: Provider(
create: (context) => 2,// 提供数据
child: const _ProviderContainer(),
),
);
}
}

class _ProviderContainer extends StatelessWidget {
const _ProviderContainer({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return Center(
// 获取数据
child: Consumer<int>(builder: (context, value, _) {
return Text(value.toString());
}),
);
}
}

ChangeNotifierProvider

提供数据共享,数据模型需要继承或者混入ChangeNotifier,并且在数据改变时调用notifyListeners,不过在实际开发中很少会只有一个属性变化,需要注意合理使用ConsumerSelector,避免不必要的刷新。

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
class ChangeNotifierProviderPage extends StatelessWidget {
const ChangeNotifierProviderPage({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(ProviderRouters.changeNotifierProvider),
),
body: ChangeNotifierProvider(
create: (context) => _State(), // 提供状态
child: const Center(child: _CounterContainer()),
),
);
}
}

class _CounterContainer extends StatelessWidget {
const _CounterContainer({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
final _state = context.read<_State>();
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
// 如果有多个属性值更新,只想在一个值改变时刷新
Selector<_State, int>(
builder: (context, value, _) {
print('text - rebuild');
return Text(value.toString());
},
selector: (_, value) => value.count,
),
TextButton(
onPressed: _state.increase,
child: const Text('+'),
),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
/// 对比Selector
Consumer<_State>(builder: (context, value, _) {
print('switch - rebuild');
return Switch(
value: value.isOpen,
onChanged: (_) {},
);
}),
TextButton(
onPressed: _state.change,
child: const Text('打开/关闭'),
),
],
)
],
);
}
}

class _State extends ChangeNotifier {
int count = 0;

bool isOpen = false;

void increase() {
count += 1;
notifyListeners();
}

void change() {
isOpen = !isOpen;
notifyListeners();
}
}

FutureProvider

设置初始值,接收一个Future,在状态更新时刷新child

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
class FutureProviderPage extends StatelessWidget {
const FutureProviderPage({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(ProviderRouters.futuerProvider),
),
body: FutureProvider.value(
value: Future.delayed(
const Duration(seconds: 2), () => _State()..loadCompleted()),
initialData: _State(),
child: Consumer<_State>(builder: (_, value, __) {
Widget container;
if (value.isLoading) {
container = const CircularProgressIndicator();
} else {
container = const Text('加载完成');
}
return Center(child: container);
}),
),
);
}
}

class _State {
bool isLoading = true;

void loadCompleted() {
isLoading = false;
}
}

StreamProvider

和FutureProvider一致,只是接收变成了一个Stream

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
class StreamProviderPage extends StatelessWidget {
const StreamProviderPage({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(ProviderRouters.streamProvider),
),
body: StreamProvider<int>(
create: (_) {
return Stream.periodic(const Duration(seconds: 1), (value) {
return value;
});
},
initialData: 0,
builder: (context, __) {
return Center(
/// 此处使用context.watch,原理和Consumer是一样的
child: Text(context.watch<int>().toString()),
);
},
),
);
}
}

ProxyProvider

如果一个模型需要依赖另外一个或者多个模型值的改变,则可以使用ProxyProvider

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
class ProxyProviderPage extends StatelessWidget {
const ProxyProviderPage({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => _Counter()),
ProxyProvider<_Counter, _Logic>(
create: (_) => _Logic(number: 0),
update: (context, value, _) {
return _Logic(number: value.count);
},
),
],
child: Scaffold(
appBar: AppBar(title: const Text('data')),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Consumer<_Counter>(
builder: (_, value, __) => Text(value.count.toString())),
Consumer<_Logic>(builder: (_, value, __) => Text(value.desc)),
Builder(builder: (context) {
return TextButton(
onPressed: () {
context.read<_Counter>().increase();
},
child: const Text('+'),
);
})
],
),
),
),
);
}
}

class _Counter extends ChangeNotifier {
int count = 0;

void increase() {
count += 1;
notifyListeners();
}
}

class _Logic {
int number;

_Logic({
required this.number,
});

String get desc => number.isEven ? "偶数" : "奇数";
}

ChangeNotifierProxyProvider

如果一个数据模型自己是一个ChangeNotifier,并且它需要依赖其他数据模型的数据,则可以使用ChangeNotifierProxyProvider,更适合使用于依赖的数据模型的数据是不变的, 当然依赖的其他数据是ChangeNotifier也是可以的。

可以查看官方demo

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
129
class ChangeNotifierProxyProviderPage extends StatelessWidget {
const ChangeNotifierProxyProviderPage({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider(create: (context) => _Letters()),
ChangeNotifierProxyProvider<_Letters, _LikeLetters>(
create: (_) => _LikeLetters(),
update: (context, value, like) {
return like!..updateLetters(value.letters);
},
),
],
child: Scaffold(
appBar: AppBar(title: const Text('data')),
body: Column(
mainAxisSize: MainAxisSize.min,
children: const [
Expanded(child: AllListView()),
Divider(),
Expanded(child: LikeListView()),
],
),
),
);
}
}

class AllListView extends StatelessWidget {
const AllListView({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
final letters =
context.select<_Letters, List<String>>((value) => value.letters);
return ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 10),
itemBuilder: (context, index) {
final letter = letters[index];
return Row(children: [
Expanded(
child: Text(letter),
),
LikeButton(
letter: letter,
),
]);
},
itemExtent: 50,
itemCount: letters.length,
);
}
}

class LikeListView extends StatelessWidget {
const LikeListView({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
final letters = context.watch<_LikeLetters>().like;
return ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 10),
itemBuilder: (context, index) {
final letter = letters[index];
return Text(letter);
},
itemExtent: 50,
itemCount: letters.length,
);
}
}

class LikeButton extends StatelessWidget {
final String letter;
const LikeButton({
Key? key,
required this.letter,
}) : super(key: key);

@override
Widget build(BuildContext context) {
final _like = context.watch<_LikeLetters>();
final isLike = _like.isLike(letter);
return TextButton(
onPressed: () {
isLike ? _like.removeLetter(letter) : _like.addLetter(letter);
},
child: Text(isLike ? "不喜欢" : "喜欢"));
}
}

class _Letters {
List<String> letters = [
'A',
'B',
'C',
'D',
'E',
];
}

class _LikeLetters extends ChangeNotifier {
late List<String> _letters;

List<String> get letters => _letters;

List<String> like = [];

void updateLetters(List<String> value) {
_letters = value;
notifyListeners();
}

bool isLike(String letter) => like.contains(letter);

void addLetter(String letter) {
like.add(letter);

notifyListeners();
}

void removeLetter(String letter) {
like.remove(letter);

notifyListeners();
}
}

GetX

项目中按照GetxController+GetBuilder基本可以大部分状态管理问题,这里只做简单使用记录,Getx有一篇文章,感兴趣的可以去看一下

Obx

一般 obs 配合 obx 使用

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
class ObxPage extends StatelessWidget {
ObxPage({Key? key}) : super(key: key);

final _count = 0.obs;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(GetXRouter.obx),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Obx(() => Text(_count.value.toString())),
TextButton(
onPressed: () => _count.value += 1,
child: const Text('+'),
)
],
)),
);
}
}

Getbuilder

一般 GetxController 和 Getbuilder配合使用

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
class GetBuilderPage extends StatelessWidget {
GetBuilderPage({Key? key}) : super(key: key);

final controller = Get.put(_Controller());

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(GetXRouter.getBuilder),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
GetBuilder<_Controller>(
builder: (controller) {
print('rebuild A');
return Text('A: ${controller.a}');
},
filter: (controller) => controller.a,
),
TextButton(
onPressed: () {
controller.increaseA();
},
child: const Text('A +')),
GetBuilder<_Controller>(
builder: (controller) {
print('rebuild B');

return Text('B: ${controller.b}');
},
filter: (controller) => controller.b,
),
TextButton(
onPressed: () {
controller.increaseB();
},
child: const Text('B +')),
],
)),
);
}
}

class _Controller extends GetxController {
var a = 0;

var b = 0;

void increaseA() {
a += 1;
update();
}

void increaseB() {
b += 1;
update();
}
}

最后

还有一些其它就不在此处做记录了,总体来说我是喜欢Riverpod的,配合hooks_riverpod你会感到什么叫做幸福的。Riverpod作为Provider的改进版本(同一作者),Provider的选择优先级可以降一降。当然Getx也很好,毕竟star量在那里。这里有一篇GetX的详细教程,作者还写有flutter_blocfish_redux等框架的使用文章,有需要可以去查看