刚接触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等框架的使用文章,有需要可以去查看