FlutterDojo设计之道—状态管理之路(五)
书接上回,我们通过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)、且不会存在线程安全问题
但是,这种封装方式一定好吗,仁者见仁智者见智
本文分享自微信公众号 - Android群英传(android_heroes)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
A3Mall v1.3 发布,新增物流查询
本次版本新增了物流查询功能,支持在后台查看订单物流信息,用户可在订单详情页查看物流状态,并且对H5页面进行了部分修改。 【新增】新增物流查询功能 【新增】新增退款日志 【修复】订单详情地区显示错误 【修复】发货单地区显示错误 【修复】修复在公众号内自动登录时,地址跳转错误问题 【修复】修复注册时验证码错误问题 【修复】修复订单详情页支付失败问题 【修改】完善商品详情页评价功能 【修改】修改商品列表布局 【修改】修改团购列表布局 【修改】修改秒杀列表布局 【修改】修改积分列表布局 【修改】修改h5订单确认页,下单失败后跳转到提示页 【修改】去除收货地址三级联动海外地址
- 下一篇
详细讲解!从JVM直到类加载器
思维导图 一、JVM介绍 在介绍JVM之前,先看一下.java文件从编码到执行的过程: 整个过程是,x.java文件需要编译成x.class文件,通过类加载器加载到内存中,然后通过解释器或者即时编译器进行解释和编译,最后交给执行引擎执行,执行引擎操作OS硬件。 从类加载器到执行引擎这块内容就是JVM。 JVM是一个跨语言的平台。从上面的图中可以看到,实际上JVM上运行的不是.java文件,而是.class文件。这就引出一个观点,JVM是一个跨语言的平台,他不仅仅能跑java程序,只要这种编程语言能编译成JVM可识别的.class文件都可以在上面运行。 所以除了java以外,能在JVM上运行的语言有很多,比如JRuby、Groovy、Scala、Kotlin等等。 从本质上讲JVM就是一台通过软件虚拟的计算机,它有它自身的指令集,有它自身的操作系统。 所以Oracle给JVM定了一套JVM规范,Oracle公司也给出了他的实现。基本上是目前最多人使用的java虚拟机实现,叫做Hotspot。使用java -version可以查看: 一些体量较大,有一定规模的公司,也会开发自己的JVM虚拟...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8编译安装MySQL8.0.19
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- Mario游戏-低调大师作品
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS关闭SELinux安全模块