佛山低价网站建设,焦作做网站推广,佛山效果好的网页设计培训在哪里,博客做网站一 、flutter 有哪些状态管理方式
Flutter的状态管理方式有很多#xff0c;Redux、 Bloc、 MobX、Provider等等。单单一个Provider#xff0c;我也见到了各种组合#xff0c;例如ChangeNotifier Provider / StateNotifier Provider#xff08; freezed#xff09;。各…
一 、flutter 有哪些状态管理方式
Flutter的状态管理方式有很多Redux、 Bloc、 MobX、Provider等等。单单一个Provider我也见到了各种组合例如ChangeNotifier Provider / StateNotifier Provider freezed。各种方式各有千秋我们根据自己的习惯和项目的情况去选择就好这里不做讨论
二 、Riverpod 介绍 Riverpod和Provider师出同门都来自作者RemiRiverpod可以被认为是Provider的重写来实现原本不可能的功能。就像它的名字一样字母与Provider相同但是又不相同。
你可以理解Riverpod是Provider的升级版解决了Provider的一些痛点
Provider是InheritedWidget的封装所以在读取状态时需要BuildContext。这导致了许多的限制许多新手在不理解InheritedWidget和BuildContext时跨页面获取状态经常会ProviderNotFoundException。而Riverpod不再依赖Flutter也就是没有使用InheritedWidget所以也不需要BuildContext。
读取对象是编译安全的。没有那么多的运行时异常。
能够有多个相同类型的provider。
provider可以是私有的。
当不再使用provider的状态时将其自动回收。
当然目前Riverpod也有一些不足0.14.03版本
毕竟诞生不久它还不能保证是完全稳定的。可能后期会有API的破坏性改动。比如在0.7.0就有不少Breaking导致我之前写的部分示例内容就报错了。目前生产环境中使用需要谨慎。
三、Riverpod的三种使用方式 Riverpod 提供了三种使用方式如下图可以根据自己的实际项目选择适合自己的方式 本篇不引入flutter_hooks相关内容这里我就选择flutter_riverpod 。那么将它添加到pubspec.yaml中
flutter_riverpod: ^0.14.03
四 、flutter 基础使用
Provider
这里使用Riverpod的Provider需要三步就可以。
import package:flutter/material.dart;
import package:flutter_riverpod/flutter_riverpod.dart;// 1.创建一个全局的provider里面储存“Hello World!”
final ProviderString helloWorldProvider Provider((_) Hello World!);void main() {runApp(// 2.添加“ProviderScope”。所有使用Riverpod的Flutter程序都必须// 在widget tree的根部添加它用来储存各个provider。ProviderScope(child: MyApp(),),);
}class MyApp extends StatelessWidget {overrideWidget build(BuildContext context) {return MaterialApp(title: Riverpod Example,theme: ThemeData(primarySwatch: Colors.blue,visualDensity: VisualDensity.adaptivePlatformDensity,),home: ProviderExample(),);}
}// 3.使用“ConsumerWidget”在“build”中获取对应的provider
class ProviderExample extends ConsumerWidget {overrideWidget build(BuildContext context, ScopedReader watch) {final String value watch(helloWorldProvider);return Scaffold(appBar: AppBar(title: Text(Provider Example)),body: Center(child: Text(value),),);}
}这里储存“Hello World!” 使用的是Provider它提供一个永远不变的对象。不过大部分场景下状态都是可变的下面用计数器来举例。
StateProvider
在“Hello World”的基础上做两点修改即可。 定义一个全局常量StateProvider。
final StateProviderint counterProvider StateProvider((_) 0);2、
import package:flutter/material.dart;
import package:flutter_riverpod/flutter_riverpod.dart;class StateProviderExample extends StatelessWidget {overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text(StateProvider Example),),body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: Widget[Text(You have pushed the button this many times:,),Consumer(builder: (context, watch, _) {/// 使用Consumer(ConsumerWidget的封装)控制刷新的范围。int count watch(counterProvider).state;return Text($count,style: Theme.of(context).textTheme.headline4,);},),],),),floatingActionButton: FloatingActionButton(/// 使用read获取counterProvider操作state。onPressed: () context.read(counterProvider).state,tooltip: Increment,child: Icon(Icons.add),),);}}如果你的状态比较复杂可以使用ChangeNotifierProvider如果习惯使用StateNotifier可以使用StateNotifierProvider 。其实StateProvider的内部是StateController也还是StateNotifier。源码如下
class StateProviderTextends AlwaysAliveProviderBaseStateControllerT, StateControllerT {StateProvider(CreateT, ProviderReference create, {String name,}) : super((ref) StateController(create(ref)), name);...
}class StateControllerT extends StateNotifierT {StateController(T state) : super(state);overrideT get state super.state;overrideset state(T value) super.state value;
}
StateNotifierProvider的用法与StateProvider基本一致这里就不贴出来了有兴趣的可以点击这里查看。
2021-04-19更新
0.14.0对StateNotifierProvider的语法有破坏性变化避免了StateNotifierProvider的错误使用。具体见文档。
ChangeNotifierProvider 这部分没啥说的注意ChangeNotifier与StateNotifier的区别需要自己调用notifyListeners通知变更。
final ChangeNotifierProviderCounter _counterProvider ChangeNotifierProvider((_) Counter());class Counter extends ChangeNotifier {int _count 0;int get count _count;void increment() {_count;notifyListeners();}void decrement(){_count--;notifyListeners();}
}class ChangeProviderNotifierExample extends StatelessWidget {overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text(ChangeNotifierProvider Example),),body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: Widget[Text(You have pushed the button this many times:,),Consumer(builder: (context, watch, _) {int count watch(_counterProvider).count;return Text($count,style: Theme.of(context).textTheme.headline4,);},),],),),floatingActionButton: FloatingActionButton(/// 使用read获取counterProvider。onPressed: () context.read(_counterProvider).increment(),tooltip: Increment,child: Icon(Icons.add),),);}
}FutureProvider
final FutureProviderString futureProvider FutureProvider((_) async {/// 延时3sawait Future.delayed(const Duration(seconds: 3));return Riverpod;
});class FutureProviderExample extends StatelessWidget {overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text(FutureProvider Example),),body: Center(child: Consumer(builder: (context, watch, _) {AsyncValueString futureProviderValue watch(futureProvider);/// 根据相应状态展示return futureProviderValue.when(loading: () CircularProgressIndicator(),error: (error, stack) Text(Oops, something unexpected happened),data: (value) Text(Hello $value,style: Theme.of(context).textTheme.headline4,),);},),),);}
}作者也提供了StreamProvider。用法大同小异有兴趣的可以查看我的示例代码。
ProviderListener 如果你希望在Widget Tree上监听provider的状态变化可以使用ProviderListener。用上面的计数器例子当计数器为5时触发监听。
ProviderListenerStateControllerint(provider: counterProvider,onChange: (_, counter) {if (counter.state 5) {print(当前计数器为5触发监听。);}},child: Consumer(builder: (context, watch, _) {int count watch(counterProvider).state;return Text($count,style: Theme.of(context).textTheme.headline4,);},),
),ScopeProvider
一般我们在实现一个列表的Item时需要传入相应的index大致如下
ListView.builder(itemCount: 50,itemBuilder: (context, index) {return ProductItem(index: index);},
)如果使用ScopedProvider并结合 ProviderScope就可以简单的获取index不必从构造方法接收它。使用起来很简单直接上代码
/// 定义ScopedProvider
final ScopedProviderint currentProductIndex ScopedProviderint(null);class ScopeProviderExample extends StatelessWidget {overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text(ScopedProvider),),body: ListView.builder(itemCount: 50,itemBuilder: (context, index) {return ProviderScope(overrides: [/// 修改valuecurrentProductIndex.overrideWithValue(index),],/// 使用const关键字实例化了“ProductItem”/// 但仍然可以在内部动态获取内容。child: const ProductItem(),);},),);}
}class ProductItem extends ConsumerWidget {const ProductItem({Key key}): super(key: key);overrideWidget build(BuildContext context, ScopedReader watch) {/// 获取相应indexfinal index watch(currentProductIndex);return ListTile(title: Text(item $index));}
}4.修饰符
family
family的作用是可以在获取provider时可以添加一个参数。直接上例子一看便知
/// 使用family可以在获取provider时传入city
final _weatherProvider Provider.familyString, String((ref, city) {return $city (Sunny);
});class FamilyExample extends StatelessWidget {overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text(Family)),body: Center(child: Consumer(builder: (context, watch, _) {/// 这里可以传参“London”final String weather watch(_weatherProvider(London));return Text($weather,);},),),);}
}意 使用family时传入的参数是有限制的。比如bool 、 int 、 double 、 String 、常量或是重写了和hashCode的不可变对象。
autoDispose 前面我们的例子中创建的provider因为保存在Widget Tree的根部。所以即使页面关闭再次进入页面时会获取之前的状态。
这显然是不灵活的那么这里就可以使用autoDispose它可以在我们不再使用provider时自动将其销毁。那么合理的使用它可以避免内存泄漏。
比如之前的计数器例子只需加一个autoDispose就可以避免此类问题。
final stateProvider StateProvider.autoDispose((_) 0);如果你需要自定义dispose事件可以使用onDispose。比如你的provider中有网络请求(使用Dio)
final myProvider FutureProvider.autoDispose((ref) async {final cancelToken CancelToken();// 当provider被销毁时取消http请求ref.onDispose(() cancelToken.cancel());// http请求final response await dio.get(path, cancelToken: cancelToken);// 如果请求成功完成则保持该状态。ref.maintainState true;return response;
});上面代码中出现了ref.maintainState这个参数默认为false。如果用户离开页面并且请求失败下次则将再次执行该请求。但是如果请求成功完成maintainState为true则将保留状态下次重新进入页面时不会触发新的请求。
使用autoDispose可以达到限制provider是全局还是局部作用。这样一来可以更方便的解决跨页面使用provider的问题。
5.进阶使用 Combining providers 1.如果创建的provider需要另一个provider的状态这时就需要使用ProviderReference的read方法。
下面的示例是给予城市和国家的provider当创建locationProvider时获取城市和国家的状态。
final ProviderString cityProvider Provider((ref) London);
final ProviderString countryProvider Provider((ref) England);
final ProviderLocation locationProvider Provider((ref) Location(ref));class Location {Location(this._ref);final ProviderReference _ref;String get label {/// read 获取final city _ref.read(cityProvider);final country _ref.read(countryProvider);return $city ($country);}
}使用Riverpod就可以提供多个相同类型的Provider这也是相比Provider的一个优点。
2.如果获取的状态值会发生变化我们需要监听它。可以使用ProviderReference的watch方法。
下面的示例是给予城市provider当城市变化时天气也相应变化。
final StateProviderString cityProvider StateProvider((ref) London);
final StateProviderString weatherProvider StateProvider((ref) {/// watch监听final String city ref.watch(cityProvider).state;return $city (Sunny);
});class CombiningProviderExample2 extends StatelessWidget {overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text(CombiningProvider)),body: Center(child: Consumer(builder: (context, watch, _) {final String weather watch(weatherProvider).state;return Text($weather,);},),),floatingActionButton: FloatingActionButton(onPressed: () {String city context.read(cityProvider).state;/// 修改状态if (city London) {context.read(cityProvider).state Xian;} else {context.read(cityProvider).state London;}},tooltip: Refresh,child: Icon(Icons.refresh),),);}
}refresh
强制provider立即刷新重新返回创建的值。这种适合列表下拉刷新或者请求数据错误时重试。 final FutureProviderListString productsProvider FutureProvider((_) async {/// 延时3sawait Future.delayed(const Duration(seconds: 3));return List.generate(50, (index) Item $index);
});class RefreshProviderExample extends StatelessWidget {overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text(RefreshProvider),),body: Center(child: Consumer(builder: (context, watch, _) {AsyncValueListString productsProviderValue watch(productsProvider);return productsProviderValue.when(loading: () CircularProgressIndicator(),error: (error, stack) Text(Oops, something unexpected happened),data: (list) RefreshIndicator(onRefresh: () context.refresh(productsProvider), /// 刷新child: ListView(children: [for (final item in list) ListTile(title: Text(item)),],),),);},),),);}
}select 当状态中某一个值发生变化时相应Consumer下的builder就会执行重建widget。如果使用select可以指定某一值更改时进行刷新精准控制刷新范围避免不必要的rebuild。
不过目前0.14.03版本select这种局部监听只支持使用hooks_riverpod包的useProvider。所以这里需要引用hooks_riverpod。
final ChangeNotifierProviderPerson personProvider ChangeNotifierProvider((_) Person());class Person extends ChangeNotifier {int _age 0;int get age _age;set age(int age) {_age age;notifyListeners();}String _name weilu;String get name _name;set name(String name) {_name name;notifyListeners();}
}class SelectExample extends StatelessWidget {overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text(Select Example),),body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: Widget[HookBuilder(builder: (_) {String name useProvider(personProvider.select((p) p.name));/// 如果使用下面的方式则age变化时这里的Text也会刷新。
// String name useProvider(personProvider).name;return Text(name:$name,);},),HookBuilder(builder: (_) {int age useProvider(personProvider.select((p) p.age));return Text(age:$age,);},),],),),floatingActionButton: FloatingActionButton(// 这里age变化时只有对应的Text会变化。onPressed: () context.read(personProvider).age Random.secure().nextInt(255),tooltip: Refresh,child: Icon(Icons.refresh),),);}
} 其他 细心的你会发现在使用read获取provider时还是使用了context。一开始不是说没有使用InheritedWidget所以也不需要BuildContext吗
其实Riverpod本身确实如此但是在Flutter的应用中为了便于高效时间复杂度O(1)的在Widget Tree中获取ProviderContainer在ProviderScope中隐式创建用来储存provider需要在根部使用InheritedWidget便于最终获取provider。
read、refresh、Consumer、ProviderListener等方法和Widget的内部其实都调用了ProviderScope.containerOf(context, listen xx);不同的是listen的值。
static ProviderContainer containerOf(BuildContext context, {bool listen true,}) {UncontrolledProviderScope scope;if (listen) {scope context //.dependOnInheritedWidgetOfExactTypeUncontrolledProviderScope();} else {scope context.getElementForInheritedWidgetOfExactTypeUncontrolledProviderScope().widget as UncontrolledProviderScope;}return scope.container;}比如read中listen的值为false使用getElementForInheritedWidgetOfExactType方法这样在数据发生变化时就不会掉用didChangeDependencies避免不必要的rebuild。相对的Consumer、ProviderListener中listen的值为ture会实现我们需要的widget重建。
我们可以还可以通过Flutter Inspector检查已有的状态所有状态汇总在ProviderScope下面这也是Riverpod的一个优点。如下图所示 发布本篇时有关Riverpod的资料与讨论很少。本篇也是我在实践完官网文档后的理解如有错误欢迎指出
个人认为Riverpod是相对更轻松便捷的一种状态管理方式待它稳定时应该能被更多的人喜爱。
Riverpod的相关示例代码我已经上传至Github有兴趣的可以看看。后面如果Riverpod有变动时我也会及时更新。大家可以收藏起来多多点赞支持一下给我点更新动力
5、参考
https://riverpod.dev/docs