稀土掘金 稀土掘金

Flutter 入门与实战(四十八):使用MultiProvider实现多状态同时管理

这是我参与8月更文挑战的第11天,活动详情查看: 8月更文挑战

前言

上一篇我们利用 Provider 对动态列表进行了改造,改造后的代码相比之前简洁了很多,代码逻辑也更清晰,具体情况可以查看上一篇: Flutter 入门与实战(四十七):使用 Provider 改造💩一样的代码,代码量降低了2/3!。本篇继续,我们来看如何完成删除功能和详情页。

删除动态

删除这个操作比较简单,我们只需要实现之前预留的删除动态方法 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类的生命周期完成相应操作。

这个情况有点类似我们之前的购物车应用(参考: Flutter 入门与实战(四十):以购物车为例初探状态管理),商品列表页和购物车页共享了部分数据,但是并不存在上下级关系。之前购物车的例子我们是将状态定义在了 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 提供的多状态可以并行使用。

屏幕录制2021-08-10 下午10.28.08.gif

总结

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


我是岛上码农,微信公众号同名,这是 Flutter 入门与实战的专栏文章。

👍🏻:觉得有收获请点个赞鼓励一下!

🌟:收藏文章,方便回看哦!

💬:评论交流,互相进步!

玻璃钢生产厂家季节性商场美陈制作陕西大型玻璃钢雕塑哪家便宜淮北人物玻璃钢雕塑定做云浮玻璃钢景观雕塑景观小品玻璃钢仿石雕塑新乡彩色玻璃钢人物雕塑厂家促销玻璃钢雕塑美人鱼义马商场美陈绿植墙玻璃钢少数民族跳舞雕塑茂名玻璃钢雕塑沙发云南玻璃钢花盆批发价格美陈商场元宵节鹤壁商场美陈绿植墙天津特色玻璃钢雕塑制作玻璃钢雕塑镀金贵阳玻璃钢园林小品雕塑威海玻璃钢广场雕塑定制安徽玻璃钢雕塑源头好货肇庆玻璃钢商场美陈商场美陈坐椅呼市雕塑玻璃钢致电内江玻璃钢广场雕塑东城区商场美陈哪家好苏州紫红色玻璃钢花盆辽宁玻璃钢动物雕塑厂家推荐阳江玻璃钢雕塑造型山东酒店玻璃钢园林艺术雕塑青岛人物玻璃钢雕塑安装江苏动物玻璃钢雕塑哪家便宜玻璃钢雕塑在城市中的应用香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警汪小菲曝离婚始末遭遇山火的松茸之乡雅江山火三名扑火人员牺牲系谣言何赛飞追着代拍打萧美琴窜访捷克 外交部回应卫健委通报少年有偿捐血浆16次猝死手机成瘾是影响睡眠质量重要因素高校汽车撞人致3死16伤 司机系学生315晚会后胖东来又人满为患了小米汽车超级工厂正式揭幕中国拥有亿元资产的家庭达13.3万户周杰伦一审败诉网易男孩8年未见母亲被告知被遗忘许家印被限制高消费饲养员用铁锨驱打大熊猫被辞退男子被猫抓伤后确诊“猫抓病”特朗普无法缴纳4.54亿美元罚金倪萍分享减重40斤方法联合利华开始重组张家界的山上“长”满了韩国人?张立群任西安交通大学校长杨倩无缘巴黎奥运“重生之我在北大当嫡校长”黑马情侣提车了专访95后高颜值猪保姆考生莫言也上北大硕士复试名单了网友洛杉矶偶遇贾玲专家建议不必谈骨泥色变沉迷短剧的人就像掉进了杀猪盘奥巴马现身唐宁街 黑色着装引猜测七年后宇文玥被薅头发捞上岸事业单位女子向同事水杯投不明物质凯特王妃现身!外出购物视频曝光河南驻马店通报西平中学跳楼事件王树国卸任西安交大校长 师生送别恒大被罚41.75亿到底怎么缴男子被流浪猫绊倒 投喂者赔24万房客欠租失踪 房东直发愁西双版纳热带植物园回应蜉蝣大爆发钱人豪晒法院裁定实锤抄袭外国人感慨凌晨的中国很安全胖东来员工每周单休无小长假白宫:哈马斯三号人物被杀测试车高速逃费 小米:已补缴老人退休金被冒领16年 金额超20万

玻璃钢生产厂家 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化