FlutterDojo设计之道—状态管理之路(五)

1,728 阅读3分钟

书接上回,我们通过InheritedWidget实现了跨Widget的数据管理。

可以发现,在使用InheritedWidget来实现数据管理的方式中,有几个东西是必须的。

  • InheritedWidget
  • 数据对象
  • 管理InheritedWidget的StatefulWidget
  • 展示View

在上篇文章中,我们使用了一个StatefulWidget来管理InheritedWidget,借助StatefulWidget的State来完成数据修改的能力,但是这种方式在使用的过程中,会发现有一些问题。

  • 业务逻辑与StatefulWidget耦合
  • 模板代码太多,写起来复杂

所以,针对上面的这些问题,实际上在封装InheritedWidget进行数据管理的时候,通常会根据职责,将代码分为几个部分。

这实际上和Android中的MVVM模式比较类似,但是由于Android原生没有响应式的能力,所以在Android上的MMVM,基本都是借助Rx或者Jetpack的方式来实现,在Flutter中,官方也没有给出一个标准的MVVM示例,其实采用哪种模式并不是关键,每个人对设计模式的理解都不相同,针对业务场景的实现方式也会有不同,所以「不管黑猫白猫,抓到老鼠就是好猫」。

下面笔者就展示一种基于InheritedWidget的封装方案。

首先,定义数据Model,它是交互数据的抽象。这里简单的使用一个类的表示。

class CustomModel {
  const CustomModel({this.value = 0});

  final int value;

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true;
    if (other.runtimeType != runtimeType) return false;
    final CustomModel otherModel = other;
    return otherModel.value == value;
  }

  @override
  int get hashCode => value.hashCode;
}

这里的Model和普通的Model相比,仅仅是重新了「==」操作符,至于为什么,后面就知道了。

接下来,同样是使用StatefulWidget来管理InheritedWidget,同时,为了更加通用,在类中增加的泛型的约定。

首先是InheritedWidget,由于它不会被外界所感知,所以设计为私有的。

class _ModelBindingScope<T> extends InheritedWidget {
  const _ModelBindingScope({Key key, this.modelBindingState, Widget child}) : super(key: key, child: child);

  final _ModelBindingState<T> modelBindingState;

  @override
  bool updateShouldNotify(_ModelBindingScope oldWidget) => true;
}

然后是StatefulWidget,它实际上相当于一层胶水,粘合了InheritedWidget和View。

在前面的文章中,对Model的处理也是在StatefulWidget的State中的,这就导致了这层胶水不够通用,所以,这里的设计思路是尽可能将这层胶水设计的更加通用。

class ModelBinding<T> extends StatefulWidget {
  ModelBinding({
    Key key,
    @required this.initialModel,
    this.child,
  })  : assert(initialModel != null),
        super(key: key);

  final T initialModel;
  final Widget child;

  _ModelBindingState<T> createState() => _ModelBindingState<T>();

  static T of<T>(BuildContext context) {
    final _ModelBindingScope<T> scope = context.dependOnInheritedWidgetOfExactType<_ModelBindingScope<T>>();
    return scope.modelBindingState.currentModel;
  }

  static void update<T>(BuildContext context, T newModel) {
    final _ModelBindingScope<T> scope = context.dependOnInheritedWidgetOfExactType<_ModelBindingScope<T>>();
    scope.modelBindingState.updateModel(newModel);
  }
}

class _ModelBindingState<T> extends State<ModelBinding<T>> {
  T currentModel;

  @override
  void initState() {
    super.initState();
    currentModel = widget.initialModel;
  }

  void updateModel(T newModel) {
    if (newModel != currentModel) {
      setState(() => currentModel = newModel);
    }
  }

  @override
  Widget build(BuildContext context) {
    return _ModelBindingScope<T>(
      modelBindingState: this,
      child: widget.child,
    );
  }
}

这个设计的核心,实际上就是这个胶水——ModelBinding,它实际上也分为两部分,即StatefulWidget和State,在StatefulWidget中,暴露了of和update两个函数,其中of函数,我们比较熟悉了,主要是update函数,它调用的是State中的updateModel函数,而这个函数,做了一个通用的处理,也就是用一个全新的model,替换当前的model。

void updateModel(T newModel) {
  if (newModel != currentModel) {
    setState(() => currentModel = newModel);
  }
}

这也就是为什么这个粘合剂可以设计的更加通用的原因。

最后来看下如何使用。

class InheritedWidgetPattern extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ModelBinding<CustomModel>(
      initialModel: const CustomModel(),
      child: Column(
        children: [
          MainTitleWidget('InheritedWidget使用的一般范式'),
          View2(),
          View1(),
        ],
      ),
    );
  }
}

class View1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final CustomModel model = ModelBinding.of<CustomModel>(context);
    return RaisedButton(
      onPressed: () {
        ModelBinding.update<CustomModel>(context, CustomModel(value: model.value + 1));
      },
      child: Text('Hello World ${model.value}'),
    );
  }
}

class View2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text('Show text ${ModelBinding.of<CustomModel>(context).value}');
  }
}

代码地址:Flutter Dojo-Backend-InheritedWidgetPattern

通过ModelBinding对需要进行管理的数据-CustomModel、以及需要这些数据的View—View1和View2,在View中,通过 ModelBinding.of<CustomModel>(context)来获取数据Model进行展示逻辑;通过ModelBinding.update<CustomModel>(context, CustomModel(value: model.value + 1)),将一个新的Model设置给粘合剂,从而达到修改数据的功能。

这种设计有两个需要注意的地方。

  • 在这种情况下,数据与View一样都是无状态的,每一次数据改动,都是使用新的Model替换原有的Model
  • Dart的垃圾回收策略可以保证这种Model替换的算法是高效的(Mark-Swap)、且不会存在线程安全问题

但是,这种封装方式一定好吗,仁者见仁智者见智