刚接触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
一般页面都会有比较复杂的逻辑,这个时候就推荐创建 CustomState
、 CustomStateNotifier\
, 以及提供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(); }); })
final sharePreference = FutureProvider<void>((ref) async { 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)); });
final appConfig = FutureProvider((ref) async { await Future.wait([ ref.watch(sharePreference.future), ref.watch(loadConfig.future), ref.watch(otherFuture.future), ]); });
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
| 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]);
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
,不过在实际开发中很少会只有一个属性变化,需要注意合理使用Consumer
和Selector
,避免不必要的刷新。
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: [ 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( 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_bloc、fish_redux等框架的使用文章,有需要可以去查看