mvp在flutter中的应用
mvp模式的优点
mvp模式将视图、业务逻辑、数据模型隔离,使用mvp模式,能使复杂的业务逻辑变得更加清晰,使代码更具有灵活性和扩展性,正是这些优点,使mvp模式广泛应用于原生开发中。
flutter使用mvp之前
以前原生开发页面,只需要花费少量的时间,就可以通过原生提供的可视化拖拽功能,迅速的完成一个简单的页面布局效果和配置,而逻辑代码只需要引用布局文件即可完成交互。然而flutter开发中目前还没有提供可视化的拖拽功能,实现页面布局和控件需要一行行码代码,因此在页面布局、元素上将会花费大量的编码时间,这对于原生开发的工程师的我来说,感觉十分不习惯。既然现在还没有提供可视化UI编辑功能,那我们也只能按照标准一行行编写UI了。flutter核心要素就是widget,所有页面元素都是widget,下面来看看使用mvp之前的代码:
import 'dart:convert'; import 'package:badge/badge.dart';
import 'package:flutter/material.dart';
import 'package:flutter_biobank/entity/IntResult.dart';
import 'package:flutter_biobank/entity/SampleResult.dart';
import 'package:flutter_biobank/entity/TextResult.dart';
import 'package:flutter_biobank/page/work/SampleCartsPage.dart';
import 'package:flutter_biobank/res/colors.dart';
import 'package:flutter_biobank/res/images.dart';
import 'package:flutter_biobank/res/urls.dart';
import 'package:flutter_biobank/util/DialogUtil.dart';
import 'package:flutter_biobank/util/HttpUtil.dart';
import 'package:flutter_biobank/util/NavigatorUtil.dart';
import 'package:flutter_biobank/widget/PageLoadView.dart';
import 'package:flutter_biobank/widget/SmartRefresh.dart';
import 'package:fluttertoast/fluttertoast.dart';
import "package:pull_to_refresh/pull_to_refresh.dart"; ///样本申领
class SampleClaimPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return new _SampleClaimPageState();
}
} class _SampleClaimPageState extends State<SampleClaimPage> {
int pageIndex = ; //当前页码
RefreshController _controller;
List<Sample> samples = new List(); //列表中的样本集合
List<Sample> checkedSamples = new List(); //选中的样本集合
int loadStatus; //当前页面加载状态
int samplesCount = ; //申领车中样本数量 @override
void initState() {
super.initState();
_controller = new RefreshController();
getSamples();
getSamplesCount();
} @override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: page_background,
appBar: new AppBar(
title: new Text("样本申领"),
actions: <Widget>[
Container(
alignment: Alignment.center,
margin: EdgeInsets.only(right: ),
child: new Badge.left(
child: IconButton(
icon: ImageIcon(AssetImage(Images.IMG_ICON_CARTS)),
onPressed: () {
NavigatorUtil.startIntent(context, new SampleCartsPage());
},
),
positionTop: ,
borderSize: ,
positionRight: ,
value: " $samplesCount "),
)
],
),
body: new PageLoadView(
status: loadStatus,
child: _buildContent(),
offstage: samples.length > ,
onTap: () {
setState(() {
loadStatus = PageLoadStatus.loading;
getSamples();
});
},
),
);
} ///构建内容显示布局
Widget _buildContent() {
return new Column(
children: <Widget>[
_buildRefresh(),
_buildBottom(),
],
);
} ///构建底部控件
Widget _buildBottom() {
return Container(
color: Colors.white,
child: Column(
children: <Widget>[
new Divider(height: 0.5, color: devider_black),
new Row(
children: <Widget>[
Expanded(child: Container(child: new Text("选中样本:${checkedSamples.length}"), padding: EdgeInsets.symmetric(horizontal: ))),
GestureDetector(
child: new Container(
alignment: Alignment.center,
color: Colors.red,
width: ,
height: ,
child: new Text("加入申领", style: new TextStyle(color: Colors.white, fontSize: ))),
onTap: () {
if (checkedSamples.length <= ) {
Fluttertoast.showToast(msg: "请选择样本");
} else {
doJoinCarts();
}
},
),
],
),
],
),
);
} ///构建刷新和加载控件
Widget _buildRefresh() {
return new SmartRefresh(
controller: _controller,
child: _buildListView(),
onRefresh: () {
//下拉刷新
pageIndex = ;
return getSamples();
},
onLoadMore: (bool) {
//上拉加载更多
getSamples();
},
);
} ///构建ListView
Widget _buildListView() {
return new ListView.builder(
physics: new AlwaysScrollableScrollPhysics(),
itemBuilder: _buildListViewItem,
itemCount: samples.length,
);
} ///构建listItem
Widget _buildListViewItem(BuildContext context, int index) {
return Card(
child: Container(
padding: EdgeInsets.symmetric(vertical: , horizontal: ),
child: new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Checkbox(
value: samples[index].isSelected,
onChanged: (bool) {
setState(() {
samples[index].isSelected = bool;
bool ? checkedSamples.add(samples[index]) : checkedSamples.remove(samples[index]);
});
}),
new Expanded(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text("${samples[index].SerialNumber}", style: new TextStyle(fontSize: )),
new Text(
"样本名称:${samples[index].Name}",
style: new TextStyle(color: Colors.black45),
softWrap: false,
overflow: TextOverflow.fade,
),
new Text("可用量:${samples[index].AvailableVolume}", style: new TextStyle(color: Colors.black45)),
new Text("位置:${samples[index].Location}", style: new TextStyle(color: Colors.black45)),
],
)),
],
),
),
);
} ///加载数据
Future getSamples() async {
await HttpUtil.getInstance().post(Urls.URL_GET_SAMPLES, data: {
"PageIndex": pageIndex,
"PageSize": ,
"State": ,
"Sort": "asc",
"BeginTime": "",
"BoxCode": "",
"EndTime": "",
"Location": "",
"Name": "",
"ProjectID": null,
"erialNumber": "",
"StudyID": null,
}, callBack: (success, data) {
_controller.sendBack(false, RefreshStatus.idle);
if (success) {
SampleResult result = SampleResult.fromJson(json.decode(data)); //解析json
if (result.code == ) {
if (pageIndex == ) {
samples.clear(); //下拉刷新时,先清除原来的数据
checkedSamples.clear(); //下拉刷新时,选中的数据也清空
}
samples.addAll(result.rows);
pageIndex += ; //数据加载成功后,page+1
samples.length >= result.total ? _controller.sendBack(false, RefreshStatus.noMore) : null;
} else {
Fluttertoast.showToast(msg: result.message);
loadStatus = PageLoadStatus.failed;
}
} else {
loadStatus = PageLoadStatus.failed;
}
setState(() {});
});
} ///加入申领车
Future doJoinCarts() async {
DialogUtil.showLoading(context); //显示加载对话框
List<int> ids = new List();
for (Sample sample in checkedSamples) {
ids.add(sample.ID);
}
await HttpUtil.getInstance().post(Urls.URL_CARTS_ADD, data: json.encode(ids), callBack: (success, data) {
Navigator.pop(context);
if (success) {
TextResult result = TextResult.fromJson(json.decode(data));
DialogUtil.showTips(context, text: result.message);
pageIndex = ;
getSamples();
getSamplesCount();
} else {
Fluttertoast.showToast(msg: data);
}
});
} ///查询申领车中样本数量
Future getSamplesCount() async {
await HttpUtil.getInstance().get(Urls.URL_CARTS_SAMPLESCOUNT, callBack: (success, data) {
if (success) {
IntResult result = IntResult.fromJson(json.decode(data));
if (result.code == ) {
setState(() {
samplesCount = result.response;
});
}
}
});
}
}
这只是一个简单的页面,调用列表查询接口,用ListView显示列表数据,调用数量查询接口,查询购物车中数量,并显示在appBar的action中。业务逻辑和页面代码混合在一起,导致代码量大,类看起来很臃肿,如果业务逻辑更加复杂的情况下,代码阅读、代码审查及功能维护都不是件容易的事。
flutter使用mvp之后
下面我们使用mvp模式对上面的代码进行改造,首先我们先将view的改变行为抽象出来,建立viewMode
abstract class IClaimPageView {
void querySamplesSuccess(SampleResult result); void querySamplesFailed(); void queryCartsSampleCountSuccess(int count); void doJoinCartsSuccess(String message); void doJoinCartsFailed(String message);
}
该类定义的方法分别表示列表查询成功或失败了,查询数量成功了,加入购物车成功或失败了,页面分别要做的各种事情,具体页面要做什么变化,就交给View去实现,也就是页面View,实现viewModel。
class _SampleClaimPageState extends State<SampleClaimPage> implements IClaimPageView {
@override
void querySamplesSuccess(SampleResult result) {
//TODO
} @override
void querySamplesFailed() {
//TODO
} @override
void queryCartsSampleCountSuccess(int count) {
//TODO
} @override
void doJoinCartsFailed(String message) {
//TODO
} @override
void doJoinCartsSuccess(String message) {
//TODO
}
}
接下来,我们需要将业务逻辑代码分离出去,建立Presenter类,传入viewModel的引用,并定义方法实现业务逻辑。
///样本申领presenter
class ClaimPresenter extends BasePresenter {
ClaimModel _model;
IClaimPageView _view; ClaimPresenter(this._view) {
_model = new ClaimModel();
} ///分页查询库存中的样本
Future querySamples(int pageIndex) async {
await _model.querySamples({
"PageIndex": pageIndex,
"PageSize": ,
"State": ,
"Sort": "asc",
"BeginTime": "",
"BoxCode": "",
"EndTime": "",
"Location": "",
"Name": "",
"ProjectID": null,
"erialNumber": "",
"StudyID": null,
}, (bool, result) {
if (_view == null) {
return;
}
if (bool) {
_view.querySamplesSuccess(result);
} else {
_view.querySamplesFailed();
}
});
} ///查询申领车中样本数量
Future queryCartsSampleCount() async {
await _model.queryCartsSampleCount((bool, int) {
if (_view == null) {
return;
}
if (bool) {
_view.queryCartsSampleCountSuccess(int);
}
});
} ///加入申领车
Future doJoinCarts(BuildContext context, List<Sample> samples) async {
DialogUtil.showLoading(context);
List<int> ids = new List();
for (Sample sample in samples) {
ids.add(sample.ID);
}
await _model.doJoinCarts(json.encode(ids), (bool, message) {
Navigator.pop(context);
if (_view == null) {
return;
}
if (bool) {
_view.doJoinCartsSuccess(message);
} else {
_view.doJoinCartsFailed(message);
}
});
} @override
void dispose() {
_view = null;
}
}
这里的ClaimModel 实际上就是数据请求代码,原本数据请求也是可以写在presenter类中的,但是为了使代码更具灵活性和解耦性,我们这里将数据请求层也抽取出去,这样我其它页面也需要查询购物车中样本数量时,只需要几句简单的代码即可实现。
class ClaimModel {
///查询样本列表
Future querySamples(data, Function(bool, Object) callBack) async {
await HttpUtil.getInstance().post(Urls.URL_GET_SAMPLES, data: data, callBack: (success, data) {
if (success) {
SampleResult result = SampleResult.fromJson(json.decode(data)); //解析json
if (result.code == ) {
callBack(true, result);
} else {
callBack(false, result.message);
}
} else {
callBack(false, data);
}
});
} ///查询申领车中的样本数量
Future queryCartsSampleCount(Function(bool, int) callBack) async {
await HttpUtil.getInstance().get(Urls.URL_CARTS_SAMPLESCOUNT, callBack: (success, data) {
if (success) {
IntResult result = IntResult.fromJson(json.decode(data));
if (result.code == ) {
callBack(true, result.response);
}
}
});
} ///加入申领车
Future doJoinCarts(data, Function(bool, String) callBack) async {
await HttpUtil.getInstance().post(Urls.URL_CARTS_ADD, data: data, callBack: (success, data) {
if (success) {
TextResult result = TextResult.fromJson(json.decode(data));
callBack(true, result.message);
} else {
callBack(true, data);
}
});
}
}
最后就是View的完整代码了
import 'package:badge/badge.dart';
import 'package:flutter/material.dart';
import 'package:flutter_biobank/entity/SampleResult.dart';
import 'package:flutter_biobank/page/work/SampleCartsPage.dart';
import 'package:flutter_biobank/page/work/claim/ClaimPresenter.dart';
import 'package:flutter_biobank/page/work/claim/IClaimPageView.dart';
import 'package:flutter_biobank/res/colors.dart';
import 'package:flutter_biobank/res/images.dart';
import 'package:flutter_biobank/util/DialogUtil.dart';
import 'package:flutter_biobank/util/Logger.dart';
import 'package:flutter_biobank/util/NavigatorUtil.dart';
import 'package:flutter_biobank/widget/PageLoadView.dart';
import 'package:flutter_biobank/widget/SmartRefresh.dart';
import 'package:fluttertoast/fluttertoast.dart';
import "package:pull_to_refresh/pull_to_refresh.dart"; ///样本申领
class SampleClaimPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return new _SampleClaimPageState();
}
} class _SampleClaimPageState extends State<SampleClaimPage> implements IClaimPageView {
static final String TAG = "SampleClaimPageState";
int pageIndex = ; //当前页码
RefreshController _controller; //控制器,控制加载更多的显示状态
List<Sample> samples = new List(); //列表中的样本集合
List<Sample> checkedSamples = new List(); //选中的样本集合
int loadStatus; //当前页面加载状态
int samplesCount = ; //申领车中样本数量
ClaimPresenter _presenter; @override
void initState() {
super.initState();
Logger.log(TAG, "initState");
_controller = new RefreshController();
_presenter = new ClaimPresenter(this);
_presenter.querySamples(pageIndex);
_presenter.queryCartsSampleCount();
} @override
void dispose() {
super.dispose();
if (_presenter != null) {
_presenter.dispose();
_presenter = null;
}
} @override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: page_background,
appBar: new AppBar(
title: new Text("样本申领"),
actions: <Widget>[
Container(
alignment: Alignment.center,
margin: EdgeInsets.only(right: ),
child: _buildBadge(context),
)
],
),
body: _buildBody(),
);
} ///构建购物车按钮
Widget _buildBadge(BuildContext context) {
if (samplesCount > ) {
return new Badge.left(
child: IconButton(
icon: ImageIcon(AssetImage(Images.IMG_ICON_CARTS)),
onPressed: () {
NavigatorUtil.startIntent(context, new SampleCartsPage());
},
),
positionTop: ,
borderSize: ,
positionRight: ,
value: " $samplesCount ");
} else {
return new IconButton(
icon: ImageIcon(AssetImage(Images.IMG_ICON_CARTS)),
onPressed: () {
NavigatorUtil.startIntent(context, new SampleCartsPage());
},
);
}
} ///构建body
Widget _buildBody() {
return new PageLoadView(
status: loadStatus,
child: new Column(
children: <Widget>[
_buildRefresh(),
_buildBottom(),
],
),
offstage: samples.length > ,
onTap: () {
setState(() {
loadStatus = PageLoadStatus.loading;
_presenter.querySamples(pageIndex);
});
},
);
} ///构建底部控件
Widget _buildBottom() {
return Container(
color: Colors.white,
child: Column(
children: <Widget>[
new Divider(height: 0.5, color: devider_black),
new Row(
children: <Widget>[
Expanded(child: Container(child: new Text("选中样本:${checkedSamples.length}"), padding: EdgeInsets.symmetric(horizontal: ))),
GestureDetector(
child: new Container(
alignment: Alignment.center,
color: Colors.red,
width: ,
height: ,
child: new Text("加入申领", style: new TextStyle(color: Colors.white, fontSize: ))),
onTap: () {
if (checkedSamples.length <= ) {
Fluttertoast.showToast(msg: "请选择样本");
} else {
_presenter.doJoinCarts(context, checkedSamples);
}
},
),
],
),
],
),
);
} ///构建刷新和加载控件
Widget _buildRefresh() {
return new SmartRefresh(
controller: _controller,
child: _buildListView(),
onRefresh: () {
return _presenter.querySamples(pageIndex = ); //下拉刷新
},
onLoadMore: (bool) {
//上拉加载更多
_presenter.querySamples(pageIndex);
},
);
} ///构建ListView
Widget _buildListView() {
return new ListView.builder(
physics: new AlwaysScrollableScrollPhysics(),
itemBuilder: _buildListViewItem,
itemCount: samples.length,
);
} ///构建listItem
Widget _buildListViewItem(BuildContext context, int index) {
return Card(
child: Container(
padding: EdgeInsets.symmetric(vertical: , horizontal: ),
child: new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Checkbox(
value: samples[index].isSelected,
onChanged: (bool) {
setState(() {
samples[index].isSelected = bool;
bool ? checkedSamples.add(samples[index]) : checkedSamples.remove(samples[index]);
});
}),
new Expanded(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text("${samples[index].SerialNumber}", style: new TextStyle(fontSize: )),
new Text(
"样本名称:${samples[index].Name}",
style: new TextStyle(color: Colors.black45),
softWrap: false,
overflow: TextOverflow.fade,
),
new Text("可用量:${samples[index].AvailableVolume}", style: new TextStyle(color: Colors.black45)),
new Text("位置:${samples[index].Location}", style: new TextStyle(color: Colors.black45)),
],
)),
],
),
),
);
} @override
void querySamplesSuccess(SampleResult result) {
//下拉刷新需要先清空列表数据
if (pageIndex == ) {
samples.clear();
checkedSamples.clear();
}
samples.addAll(result.rows);
//判断是不是最后一页
pageIndex * >= result.total ? _controller.sendBack(false, RefreshStatus.noMore) : _controller.sendBack(false, RefreshStatus.idle);
pageIndex += ;
setState(() {});
} @override
void querySamplesFailed() {
//查询失败,修改页面状态
_controller.sendBack(false, RefreshStatus.idle);
loadStatus = PageLoadStatus.failed;
setState(() {});
} @override
void queryCartsSampleCountSuccess(int count) {
setState(() {
samplesCount = count;
});
} @override
void doJoinCartsFailed(String message) {
Fluttertoast.showToast(msg: message);
} @override
void doJoinCartsSuccess(String message) {
DialogUtil.showTips(context, text: message);
_presenter.querySamples(pageIndex = );
_presenter.queryCartsSampleCount();
}
}
经过改动之后,会发现程序的类变多了,代码量视乎更大了。但是我们并不是以代码量的多少来评价代码的质量,往往是以代码的可阅读性和可变性来评价。经过改动之后,SampleClaimPage 类主要负责UI的实现和UI与数据的绑定及交互。ClaimPresenter类主要负责业务逻辑的实现和数据与UI之间交互的建立。而ClaimModel 类只需要简单的实现数据的获取。代码逻辑变得十分清晰。
结束语
代码模式的设计需要便于程序员理解代码,mvp模式特别适用于页面逻辑较为复杂的情况。当页面逻辑十分简单只时,就无需为了设计而设计,也就是代码界的一句金玉良言:“不要过度设计”。欢迎大家指正。
mvp在flutter中的应用的更多相关文章
- mvp 在 flutter 中的应用
在 Android 应用程序开发过程中,我们经常会用到一些所谓的架构方法,如:mvp,mvvm,clean等.之所以这些方法会被推崇是因为他们可以大大的解耦我们的代码的功能模块,让我们的代码在项目中后 ...
- 在Flutter中嵌入Native组件的正确姿势是...
引言 在漫长的从Native向Flutter过渡的混合工程时期,要想平滑地过渡,在Flutter中使用Native中较为完善的控件会是一个很好的选择.本文希望向大家介绍AndroidView的使用方式 ...
- 从零学习Fluter(五):Flutter中手势滑动拖动已经网络请求
从六号开始搞Flutter,到今天写这篇blog已经过了4天时间,文档初步浏览了一遍,写下了这个demo.demo源码分享在github上,现在对flutter有种说不出的喜欢了.大家一起搞吧! 废话 ...
- Flutter 中 JSON 解析
本文介绍一下Flutter中如何进行json数据的解析.在移动端开发中,请求服务端返回json数据并解析是一个很常见的使用场景.Android原生开发中,有GsonFormat这样的神器,一键生成Ja ...
- Flutter 中文文档网站 flutter.cn 正式发布!
在通常的对 Flutter 介绍中,最耳熟能详的是下面四个特点: 精美 (Beautiful):充分的赋予和发挥设计师的创造力和想象力,让你真正掌控屏幕上的每一个像素. ** 极速 (Fast)**: ...
- 理解 Flutter 中的 Key
概览 在 Flutter 中,大概大家都知道如何更新界面视图: 通过修改 Stata 去触发 Widget 重建,触发和更新的操作是 Flutter 框架做的. 但是有时即使修改了 State,Flu ...
- flutter 中的样式
flutter 中的样式 样式 值 width 320.0 height 240.0 color Colors.white,Colors.grey[300] textAlign TextAlign.c ...
- flutter中的异步机制Future
饿补一下Flutter中Http请求的异步操作. Dart是一个单线程语言,可以理解成物理线路中的串联,当其遇到有延迟的运算(比如IO操作.延时执行)时,线程中按顺序执行的运算就会阻塞,用户就会感觉到 ...
- Flutter中管理路由栈的方法和应用
原文地址:https://www.jianshu.com/p/5df089d360e4 本文首先讲的Flutter中的路由,然后主要讲下Flutter中栈管理的几种方法. 了解下Route和Navig ...
随机推荐
- centos7.2 部署zabbix 3.2.7
centos7.2 部署zabbix 3.2.7[zabbix@zabbixServer ~]$ cat /etc/redhat-release CentOS Linux release 7.2.15 ...
- 阿里Java开发规范&谷歌Java开发规范&华为Java开发规范&Tab键和空格比较&Eclipse的Tab键设置 总结
现在收集到如下有用的信息: 阿里巴巴公开的Java开发规范:https://yq.aliyun.com/articles/69327?utm_content=m_10088 google公开的Java ...
- 工具-CocoaPods安装和使用及卸载
CocoaPods是iOS最常用的第三方类库管理工具,绝大部分有名的开源类库都支持CocoaPods. 我们在使用gem更新的时候,经常会为速度抓狂,其实gem默认的源是https://rubygem ...
- 全网最详细的Windows系统里Oracle 11g R2 Database服务器端(64bit)的下载与安装(图文详解)
不多说,直接上干货! 环境: windows10系统(64位) 最好先安装jre或jdk(此软件用来打开oracle自带的可视化操作界面,不装也没关系:可以安装plsql,或者直接用命令行操作) Or ...
- wordpress添加文章阅读数量
将下面代码添加到functions.php //取得文章的阅读次数 function post_views($before = '点击 ', $after = ' 次', $echo = 1) { g ...
- Wookmark-jQuery-master 瀑布流插件使用介绍,含个人测试DEMO
要求 必备知识 本文要求基本了解 Html/CSS, JavaScript/JQuery. 开发环境 Dreamweaver CS6 / Chrome浏览器 演示地址 演示地址 资料下载 测试预 ...
- postgresql的启停和创建
一.启停方法 两种方法 1.直接运行postgres进程启动: 2.使用pg_ctl命令启动 postgres -D /home/osdba/pgdata & 二.停止数据库的三种模式 sm ...
- postman环境变量 全局变量清理
一:主要内容 清除一个环境变量.全局变量 清除全部环境变量.全局变量 清除部分环境变量.全局变量 二:清除一个指定环境变量.全局变量 1. 清除一个环境变量,如清除用户名环境变量,username为变 ...
- SpringMVC源码阅读:属性编辑器、数据绑定
1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...
- postman学习笔记(二)
昨天刚操作了一遍最简单的接口测试,今天就收到了俩json文件,一个是postman里导出的接口列表一个是环境变量.拿到的时候一脸懵逼,昨天还以为学会用postman测试接口了,今天才发现哪儿到哪儿呀. ...