写点什么

使用 Provider 实现 Flutter 多组件的状态共享

作者:岛上码农
  • 2022 年 5 月 20 日
  • 本文字数:2832 字

    阅读完需:约 9 分钟

使用 Provider 实现 Flutter 多组件的状态共享

前言

上一篇我们利用 Provider 对动态列表进行了改造,改造后的代码相比之前简洁了很多,代码逻辑也更清晰。本篇继续,我们来看如何完成删除功能和详情页。

删除动态

删除这个操作比较简单,我们只需要实现之前预留的删除动态方法 removeWithId 即可。这个方法首先向后端请求删除接口,删除成功后将元素从列表数据_dynamics移除即可。


void removeWithId(String id) async {  var response = await DynamicService.delete(id);  if (response?.statusCode == 200) {    _dynamics.removeWhere((element) => element.id == id);    notifyListeners();  } else {    EasyLoading.showError(response?.statusMessage ?? '删除失败');  }}
复制代码


这里我们用到了 Dart 数组的 removeWhere 方法来删除 id 匹配的元素。捎带讲一下 Dart 的数组操作,Dart 提供了丰富的数组操作方法,方法定义在List<E>类中,常见的有:


  • generate:使用指定的长度产生固定的数组,非常适用于 Mock 数据。

  • forEach:按元素展开,分布处理每一个元素。

  • map:将元素展开转换为另一个可迭代对象。

  • addinsertremoveremoveXX:数据的添加,插入、删除指定元素和按 XX 条件删除元素。

  • indexWhere:按回调函数的条件超找满足条件的元素的位置。

  • fisrtWhere:找到符合条件的第一个元素,如果找不到会返回符合第二个条件的首个元素,第二个参数如果没设置也没找到会抛异常。

  • retainWhere:只保留符合条件的元素。

  • sort:按给定的回调比较函数排序。

  • 其他请参考 List<E>类中数组操作方法的定义。

动态详情

动态详情的改造有点麻烦,这其中有两个原因:


  • 动态详情并不是列表页面的子组件,也就没法直接和列表共享状态管理,意味着状态管理需要从更高层级定义。

  • 动态详情需要请求网络数据后才能够显示,而且还需要更新阅读数。这就意味着动态详情存在着时序操作,这种操作 StatelessWidget 满足不了,因此需要使用 StatefulWidget,在对应的State类的生命周期完成相应操作。


这个情况有点类似我们之前的购物车应用,商品列表页和购物车页共享了部分数据,但是并不存在上下级关系。之前购物车的例子我们是将状态定义在了 main.dartrunApp 方法中,使得购物车的状态处于了最顶级组件中。目前看来我们的动态状态管理也需要定义在 main.dartrunApp 方法里。可是已经有了一个状态管理组件了,这个时候怎么办?


这个时候 ProviderMultiProvider 就派上用场了!MultiProvider 专门为了解决多状态共存的情况而设计,用法如下:


MultiProvider({  Key? key,  providers: List<SingleChildWidget>,   Widget? child,   TransitionBuilder? builder})
复制代码


  • providers:一组 Provider 对象,用于下级组件依赖多个状态的情况。

  • child:依赖状态管理的下级子组件。

  • builder:用于直接获取状态数据的语法糖。


我们改造一下 runApp 方法的调用:


runApp(MultiProvider(  providers: [    ChangeNotifierProvider(create: (context) => CartModel()),    ChangeNotifierProvider(create: (context) => DynamicModel()),  ],  child: MyApp(),));
复制代码


这样在顶级组件 MyApp 上就有了两个状态了,这些状态可以给所有下级的组件使用。然后来改造动态详情代码 DynamicDetailPage 类。上面讲过,这个类还需要保留为 StatefulWidget,只是我们不再使用 setState 方法更新界面了。


initState 的方法中我们需要请求详情数据,从而保证只请求一次数据。请求成功后状态管理会自动刷新界面。同时请求成功后我们还需要更新浏览数量。这个我们使用了Futurethen 方法,在获取动态成功后(方法返回 true)才更新浏览数量。请求详情数据和更新浏览数都属于业务代码,我们放入到 DynamicModel 里,同时我们在 DynamicModel 增加了一个_currentDynamic 用于详情页或后续的编辑页面。


Future<bool> getDynamic(String id) async {  EasyLoading.showInfo('加载中...', maskType: EasyLoadingMaskType.black);  if (_currentDynamic?.id != id) {    _currentDynamic = null;  }  var response = await DynamicService.get(id);  if (response != null && response.statusCode == 200) {    _currentDynamic = DynamicEntity.fromJson(response.data);    notifyListeners();
EasyLoading.dismiss(); return true; } else { EasyLoading.showInfo(response.statusMessage); return false; }}
void updateViewCount(String id) async { var response = await DynamicService.updateViewCount(id); if (response != null && response.statusCode == 200) { _currentDynamic.viewCount = response.data['viewCount']; // 如果元素在列表中,则更新 int currentIndex = dynamics.indexWhere((element) => element.id == _currentDynamic.id); if (currentIndex != -1) { _dynamics[currentIndex] = _currentDynamic; } notifyListeners(); }}
复制代码


详情页面就比较简单了,我们在initState 调用 DynamicModelgetDynamic方法获取详情,如果成功更新界面,并且更新浏览数,这里我们只贴出部分代码:


class DynamicDetailPage extends StatefulWidget {  final String id;  DynamicDetailPage(this.id, {Key key}) : super(key: key);
_DynamicDetailState createState() => _DynamicDetailState();}
class _DynamicDetailState extends State<DynamicDetailPage> { @override void initState() { super.initState(); context.read<DynamicModel>().getDynamic(widget.id).then((success) { if (success) { context.read<DynamicModel>().updateViewCount(widget.id); } }); }
@override Widget build(BuildContext context) { DynamicEntity currentDynamic = context.watch<DynamicModel>().currentDynamic; return Scaffold( appBar: AppBar( title: Text('动态详情'), brightness: Brightness.dark, ), body: currentDynamic == null ? Center( child: Text('请稍候...'), ) : _getDetailWidget(currentDynamic), ); } //..}
复制代码


改造完,整个代码也缩减了几十行,业务上也更加清晰了。

运行结果

运行结果如下图,可以看到动态模块和购物车模块都能正常运行,说明 MultiProvider 提供的多状态可以并行使用。


总结

本篇介绍了动态模块的删除和详情的优化改造,通过使用 MultiProvider,我们能够实现多状态共同管理,为 App 的子组件提供多个状态,从而避免状态管理类的代码揉和不同类业务,导致业务代码过于臃肿。当然,这里也有一个缺陷,那就是如果我们的 App 比较庞大,不可能都将状态管理提升到 main 方法的 runApp 来,这样会导致整个 App 挂载的状态过多。对于这种方式又该如何处理,我们接下来会在后续的动态模块代码改造中解决这个问题。


发布于: 刚刚阅读数: 3
用户头像

岛上码农

关注

用代码连接孤岛,公众号@岛上码农 2022.03.03 加入

从南漂到北,从北漂到南的业余码农

评论

发布
暂无评论
使用 Provider 实现 Flutter 多组件的状态共享_flutter_岛上码农_InfoQ写作社区