Loading...
墨滴

岛上码农@公众号同名

2021/11/09  阅读:11  主题:橙心

Flutter 使用 Provider 实现嵌套状态管理

前言

上一篇使用 MultiProvider 进行多页面共享状态数据中,我们介绍了使用 Provider 改造动态模块完成删除和详情页面的逻辑。其实改造过程中有几个地方有些别扭,比如:

  • 我们在 DynamicModel 类中增加了一个 _currentDynamic用于详情页面,而这个对象其实和列表没太大关系,只是用于详情页面而已。
  • updateViewCount这个方法也是和单个动态类有关的。

这样导致我们列表页面的业务和详情的业务耦合在一起了,如果我们的添加、更新以及其他和动态相关的业务都丢导致这个状态管理类来的话,又会导致状态管理类十分臃肿,也违背了单一职责原则。这个时候怎么办?

自然我们会想到拆分,是的,这个时候我们需要将不同界面的状态拆分出来。本篇我们以添加页面为例,讲述嵌套的状态管理。

添加页面的状态管理

我们先新增一个添加页面的状态管理类 DynamicAddModel,然后将之前添加页面的业务代码挪进去。具体的属性和定义的方法如下:

  • Map<String, Map<String, Object>> _formData:表单配置 Map,用来动态配置添加页面的表单。
  • File_imageFile:选择的图片文件。
  • DynamicEntity _currentDynamic:添加成功后的动态对象,用于添加成功后添加到列表,从而避免列表再次刷新。
  • handleTextFieldChanged(String formKey, String value):输入框内容改变后,更新表单数据的处理回调方法;
  • handleClear(String formKey):输入框清除按钮点击时的处理回调方法;
  • handleImagePicked(File imageFile):图片选择成功后的处理回调方法;
  • Future<bool> handleSubmit():表单提交方法,这里返回的是一个 Future<bool>对象,当成功时返回true,失败时返回 false。以便界面做后续的处理,这里是成功后要向动态列表中添加数据,并返回到列表页面。

添加页面的改造

有了状态管理之后,添加页面就极其简单了,区别在于我们需要使用一个 DynamicAddWrapper 组件包裹真实的添加页面,以便在添加页面的上级使用Provider实现添加页面的状态管理。代码如下所示:

class _DynamicAdd extends StatelessWidget {
  _DynamicAdd({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final watchState = context.watch<DynamicAddModel>();
    final readState = context.read<DynamicAddModel>();
    return Scaffold(
      appBar: AppBar(
        title: Text('添加动态'),
        brightness: Brightness.dark,
      ),
      body: DynamicForm(
        watchState.formData,
        readState.handleTextFieldChanged,
        readState.handleClear,
        '提交',
        () {
          readState.handleSubmit().then((success) {
            if (success) {
              context.read<DynamicModel>().add(readState.currentDynamic);
              Navigator.of(context).pop();
            }
          });
        },
        readState.handleImagePicked,
        imageFile: watchState.imageFile,
      ),
    );
  }
}

class DynamicAddWrapper extends StatelessWidget {
  DynamicAddWrapper({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => DynamicAddModel(),
      child: _DynamicAdd(),
    );
  }
}

注意这里我们把添加页面_DynamicAdd变成了文件内部类,这是因为加入了状态管理之后,必须要有上级的状态管理包裹组件才可以使用。封装为内部类可以避免外部直接使用该组件报错。对于_DynamicAdd类,我们做了如下改造:

  • 将业务代码移到状态管理类中;
  • 将组件由 StatefulWidget 改为了 StatelessWidget
  • 将表单的回调处理方法替换为状态管理对象的对应方法;

嵌套状态的使用

在动态添加页面,我们在提交成功后的处理代码如下:

readState.handleSubmit().then((success) {
  if (success) {
    context.read<DynamicModel>().add(readState.currentDynamic);
    Navigator.of(context).pop();
  }
});

这里首先是调用了 DynamicAddModelhandleSubmit 方法,然后在 Futurethen回调方法里调用了动态列表的状态管理对象 DynamicModel,也就是使用了上面层级的状态管理,相当于我们同时使用了不同层级的状态管理,即嵌套使用了。这是因为 Provider 本身是对 InheritedWidget 的封装,因此多级嵌套其实和 Widget 的嵌套一样,是可以这么使用的。

通过将添加页面的状态抽离后,是不是感觉整个UI 层的代码清爽了很多?而对于团队协作来说,在我们约定好 UI 层的接口后,可以将开发工作拆分为业务代码和 UI 代码看,从而实现分工协作——不过,哪个老板会让搞 App 开发的只写业务或只写界面呢?他们要的都是下图这种大神!

image.png
image.png

总结

代码已经提交至:状态管理篇章源码。使用嵌套的状态管理可以将同一模块不同业务的状态管理进一步抽离,从而避免了状态管理代码的臃肿,使得代码符合单一职责原则。不过,我们回过头来看动态添加和更新页面的代码,会发现里面有90%的代码都是相似的,这种如果拆分感觉又有点重复了,那如果要共用的话又该怎么做呢?下一篇我们通过动态的更新页面来介绍Provider的状态管理对象的复用。

岛上码农@公众号同名

2021/11/09  阅读:11  主题:橙心

作者介绍

岛上码农@公众号同名