内容简介:直奔主题最开始学习flutter的时候,我们可能把ui层和业务逻辑层写在了一起,慢慢的dart文件越来越大,里面的逻辑也越来越复杂,然后我们就会想到,是不是应该把代码重构一遍了? 首先,代码是尽量职责单一的才好,这样有问题也容易修改,不会牵一发而动全身,在开发android的时候,我用过mvp,用过mvvm,个人比较喜欢mvvm,要说这两个的区别,首先mvp模式是当你获取到数据以后,你需要自己控制如何刷新ui。而mvvm是把数据和ui绑定到了一起,当你的数据改变的时候,ui自己就会改变。这个区别是个人的理解
直奔主题
最开始学习flutter的时候,我们可能把ui层和业务逻辑层写在了一起,慢慢的dart文件越来越大,里面的逻辑也越来越复杂,然后我们就会想到,是不是应该把代码重构一遍了? 首先,代码是尽量职责单一的才好,这样有问题也容易修改,不会牵一发而动全身,在开发android的时候,我用过mvp,用过mvvm,个人比较喜欢mvvm,要说这两个的区别,首先mvp模式是当你获取到数据以后,你需要自己控制如何刷新ui。而mvvm是把数据和ui绑定到了一起,当你的数据改变的时候,ui自己就会改变。这个区别是个人的理解,如有错误请纠正。
然后我们来说,如何在flutter中使用mvvm设计模式来让ui层和业务逻辑层解耦,我们先看一段没有使用mvvm设计模式的代码,所有代码已经上传到了 github
main.dart
void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter MVVM Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: HomePageNoMVVM(), ); } } 复制代码
page_home_no_mvvm.dart
///没有使用MVVM设计模式的Widget ///author:liuhc class HomePageNoMVVM extends StatefulWidget { @override _HomePageState createState() => _HomePageState(); } class _HomePageState extends State<HomePageNoMVVM> { bool _loading = true; String _text; @override void initState() { super.initState(); loadData(); } void loadData() { NetWork.query().then((String text) { setState(() { _loading = false; _text = text; }); }).catchError((error) { setState(() { _loading = false; _text = error.toString(); }); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Flutter没有使用MVVM的示例"), ), body: Center( child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ RaisedButton( child: Text("点击重新获取网络数据"), onPressed: () { loadData(); }, ), Offstage( offstage: !_loading, child: CircularProgressIndicator(), ), Expanded( child: SingleChildScrollView( child: Text("${_text ?? ""}"), ), ), ], ), ), ); } } 复制代码
可以看到,进入页面的时候,我们需要请求数据,获取到数据以后,我们再调用setState刷新页面,然后就显示出来了获取到的数据,这段代码 功能是正常的,但是代码不是优雅的 ,因为ui层既需要控制如何显示ui,又需要和业务层打交道,从业务层获取数据后自己再更新ui,这明显违反了职责单一的原则,当这种逻辑越来越多,以后维护就越来越困难,然后我们来看一下,如何用mvvm设计模式重构这段代码
1. 首先我们创建一个ViewModel的基类
abstract_base_viewmodel.dart
import 'package:flutter/widgets.dart'; ///所有viewModel的父类,提供一些公共功能 ///author:liuhc abstract class BaseViewModel { bool _isFirst = true; bool get isFirst=>_isFirst; @mustCallSuper void init(BuildContext context) { if (_isFirst) { _isFirst = false; doInit(context); } } ///获取数据 @protected Future refreshData(BuildContext context); @protected void doInit(BuildContext context); void dispose(); } 复制代码
这个类,我封装了基本所有viewModel都需要的一些方法,那个 init
方法的作用是为了保证 doInit
只执行一次,这样做省去了所有子类都判断一下是否已经执行过 init
,子类只需要重写 doInit
就可以保证方法里的代码只执行一次。
2. 然后,我们创建一个Widget,这个Widget里,有一个类属性为ViewModel的实例
viewmodel_provider.dart
import 'package:flutter/material.dart'; import 'package:flutter_mvvm/core/abstract_base_viewmodel.dart'; ///提供viewModel的widget ///author:liuhc class ViewModelProvider<T extends BaseViewModel> extends StatefulWidget { final T viewModel; final Widget child; ViewModelProvider({ @required this.viewModel, @required this.child, }); static T of<T extends BaseViewModel>(BuildContext context) { final type = _typeOf<ViewModelProvider<T>>(); ViewModelProvider<T> provider = context.ancestorWidgetOfExactType(type); return provider.viewModel; } static Type _typeOf<T>() => T; @override _ViewModelProviderState createState() => _ViewModelProviderState(); } class _ViewModelProviderState extends State<ViewModelProvider> { @override Widget build(BuildContext context) { return widget.child; } @override void dispose() { widget.viewModel.dispose(); super.dispose(); } } 复制代码
3. 完成
是的就是这么简单,我们创建了2个类,就完成了我们的MVVM设计模式的框架
4. 使用
下面我们来看看,如何用这个mvvm的框架重构我们刚才的代码
4.1 先编写我们的ViewModel类,这里我使用了rxdart,主要是BehaviorSubject可以保存最后一次发送的数据,不过这里没有用到这个特性,你就把它当成StreamController就可以了
viewmodel_home.dart
import 'package:flutter/material.dart'; import 'package:flutter_mvvm/core/abstract_base_viewmodel.dart'; import 'package:flutter_mvvm/core/network.dart'; import 'package:rxdart/rxdart.dart'; ///首页ViewModel类,用来和业务层交互 ///author:liuhc class HomeViewModel extends BaseViewModel { // ignore: close_sinks BehaviorSubject<String> _dataObservable = BehaviorSubject(); Stream<String> get dataStream => _dataObservable.stream; @override void dispose() { _dataObservable.close(); } @override void doInit(BuildContext context) { refreshData(context); } @override Future refreshData(BuildContext context) { //个人比较喜欢这样写,不然要写try catch来包裹代码,try catch不如这样写起来方便,不用一直定义变量 return NetWork.query().then((String text) { _dataObservable.add(text); }).catchError((error) { _dataObservable.addError(error); }); } } 复制代码
4.2 然后我们来重构首页Widget
page_home.dart
import 'package:flutter/material.dart'; import 'package:flutter_mvvm/core/viewmodel_provider.dart'; import 'package:flutter_mvvm/page/home/viewmodel_home.dart'; ///使用MVVM设计模式的Widget ///author:liuhc class HomePage extends StatefulWidget { @override _HomePageState createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { HomeViewModel _viewModel; @override void initState() { super.initState(); _viewModel = ViewModelProvider.of(context); _viewModel.init(context); } @override void dispose() { _viewModel.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Flutter使用MVVM的示例"), ), body: Center( child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ RaisedButton( child: Text("点击重新获取网络数据"), onPressed: () { _viewModel.refreshData(context); }, ), Expanded( child: SingleChildScrollView( child: StreamBuilder( stream: _viewModel.dataStream, builder: (BuildContext context, AsyncSnapshot<String> snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return Center( child: CircularProgressIndicator(), ); } return Text( "${snapshot.hasError ? snapshot.error : snapshot.data}", ); }, ), ), ), ], ), ), ); } } 复制代码
上面代码的关键部分是通过 ViewModelProvider.of(context);
获取到了上层Widget里的 viewModel
类实例,这部分的知识不是本文的终点,不懂的请自己查询一下相关知识。
4.3 然后我们修改程序入口,看一下如何把 首页Widget
和 首页ViewModel
绑定到一起的
main.dart
void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter MVVM Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: ViewModelProvider( viewModel: HomeViewModel(), child: HomePage(), ), ); } } 复制代码
在上面的代码里,我们的 home
没有直接传递 HomePage()
,而是传递的 ViewModelProvider
, ViewModelProvider
的代码可以在上面发过了,在 ViewModelProvider
这个类里,我们保存了 viewModel
的实例,在 ViewModelProvider
的 build
方法里,我们直接返回了传入的 child
,我们还定义了一个方法 static T of<T extends BaseViewModel>(BuildContext context)
,在这个方法里通过调用 context.ancestorWidgetOfExactType
找到了该类里的 viewModel
类属性,所以在 _HomePageState
类里我们找到了传入 ViewModelProvider
的 viewModel
,然后可以用该 viewModel
来进行下一步操作。
文章到此讲解结束,在使用该种方式开发的过程中,还能完美解决TabView隔tab点击报错的问题(用过的都知道我在说什么),因为即使使用了AutomaticKeepAliveClientMixin,挨个点击tab的话没问题,但是隔着点的话还是有问题,我也找过很多方法,都不好用,但是该种方式可以解决该问题,因为是用StreamBuilder刷新的数据,而ViewModel保存在了上层widget,所以本widget重绘的时候上层widget的viewModel的实例并不会发生变化,数据还在Stream里,所以即使重新执行了build方法,也不会再次联网请求数据,只有我们手动给StreamController add数据的时候,才会将新数据给本widget来进行重绘。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 设计模式之工厂方法模式,附Java代码示例
- 常见的设计模式多语言示例(C++, Java, Python, PHP, Perl)
- 常见的设计模式多语言示例(C++, Java, Python, PHP, Perl)
- 粒子滤波Matlab示例
- transformers示例
- 粒子滤波Matlab示例
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
信息学奥林匹克竞赛指导--组合数学的算法与程序设计PASCAL版/信息学奥林匹克竞赛指导丛书
林 生编 / 清华大学出版社 / 2002-8 / 19.00元
一起来看看 《信息学奥林匹克竞赛指导--组合数学的算法与程序设计PASCAL版/信息学奥林匹克竞赛指导丛书》 这本书的介绍吧!