1. Flutter 结合 google MVP 架构的理解
在Android 应用开发中,google 官方就给出了对应的demo 展示了mvp的结构。
google demo地址: https://github.com/googlesamples/android-architecture
mvp 的项目结构意为把 UI层和数据层进行分割,让各个层级,做对应的事情,降低耦合程度,同时提高代码可阅读性。
下图可以大概了解m-v-p 的结构关系:
针对Flutter 其实也可以进行对应的mvp架构
Flutter的mvp 架构中,View层就有所改变了,Android中是Activity ,而Flutter中就变成了 State ,为什么是State 而不是StatefulWidget 呢?
其实最主要一点就是Widget的状态表现都在State中体现,也就是说Widget的生命周期都State中。在处理业务逻辑的时候,也是离不开Widget的生命周期的,也就是离不开State的。所以State 作为View层是更加合理的。
而P层和M层其实就和Android 的MVP 没有什么区别了。
来个简单的的项目结构:
- app_views : 页面,业务相关
- base:基类
- contract:接口文件
- model:数据层文件
- presenter:P层文件
- views: 页面Widget文件
- core :数据库,网络请求相关
- utils: 工具类
- widget: 独立控件
- main.dart :应用入口
2. View--Presenter层构造
V-P层,更多是相互调用,View层处理UI展示的逻辑,业务的逻辑处理交给P层, P层处理业务逻辑,处理完进行回调给View层进行UI展示。
A. 基类
首先针对State 封装一个BaseState。BaseState 支持泛型接收 StatefuleWidget, 同时接受P层的接口。接收的 StatefuleWidget 是原来的State需要的。
BaseState :
- 对State 的 build 方法进行一个简单封装,子类只要实现 buildViews 即可。
- 然后就是 presenter ,通过getPresenter ,让子类进行实例化。
- initState 状态也进行简单封装,获取到presenter实体,并赋值给mPresenter。同时抛出一个initViewState给子类去调用。
View(State)层 就可以通过 mPersenter 调用 P层提供的接口了。
abstract class BaseState extends State {
E mPresenter;
@override
void initState() {
mPresenter = getPresenter();
initViewState();
super.initState();
}
@override
Widget build(BuildContext context) {
return buildViews(context);
}
E getPresenter();
void initViewState();
Widget buildViews(BuildContext context);
}
BasePresenter:
- 泛型接收View(State)层的接口
- 构造方法接收 View (State)实体, 赋值给 view 。方便调用View层接口
ps :其他代码忽略,其实处理M层数据需要的封装
abstract class BasePresenter extends IPresenter {
T view;
BasePresenter(IView v):view = v;
handleError(error, errorCallback callback) {
HttpIOException exception = error as HttpIOException;
//TODO 可以对报错结果进行一轮处理,例如进行Toast 提示或者其他操作
callback(exception);
}
}
typedef errorCallback = void Function(HttpIOException error);
IContract :
这个文件比较简单,就是两个接口封装. 上面 BaseState ,BasePresenter 都会对 这两个接口文件进行继承实现。
abstract class IView {}
abstract class IPresenter {}
B. 实现类
基类已经做了基本的封装,下面就是看看具体看看实现层面是怎么样的。
首先来个V-P的接口文件:
HomePageContract:
比较简单,继承IView 以及 IPresneter
abstract class IHomeView extends IView{
updateView();
}
abstract class IHomerPresenter extends IPresenter{
getData();
}
_HomePageState:
首先是继承BaseState , 泛型接收 StatefulWidget 以及 IHomerPresenter ,同时实现了 IHomeView接口
BaseState,有几个方法需要在这里复写:
- getPresenter 获取Presenter 实体
- initViewState 对应 State的initState
- buildViews 对应 State的 build
class _HomePageState extends BaseState implements IHomeView{
int _counter = 0;
@override
updateView() {
}
@override
IHomerPresenter getPresenter() {
return new HomePagePresenter(this);
}
@override
void initViewState() {
}
@override
Widget buildViews(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
new Text(
'You have pushed the button this many times:',
),
new Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: new FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: new Icon(Icons.add),
),
);
}
}
HomePagePresenter :
presenter 继承 BasePresenter,泛型接收IHomeView,实现 IHomerPresenter 接口。
这里比较简单:
- 构造方法,传入View 接口,通过IView 就可以调用V层的UI了。 在构造时候,同时进行了M层的实例化
- getData是对接口文件 IHomePresenrer的实现,供View层调用。
class HomePagePresenter extends BasePresenter implements IHomerPresenter{
IHomePageModelService homePageModelService;
HomePagePresenter(IHomeView v) : super(v) {
homePageModelService = new HomePageModel();
//可以建立多个 model 进行调用
}
@override
getData() async{
homePageModelService.getHomePageInfo("Tom", 18)
.then((data){
print("code:" + data.code.toString());
print("message:" + data.message);
print("time:" + data.payload.toString());
}).catchError((error) => handleError(error,(ioError){
print(ioError.message);
}));
}
}
到此为止, _HomePageState 就通过IPresenter 接口持有了presenter的实体,可以对P层进行逻辑操作。同时HomePagePresenter 也通过 IView ,可以回调给 State 进行UI更新了。
3. Model层
model 层的意义在于是处理数据,包括网络数据,本地数据库数据等。
在上面的 HomePagePresenter , 构造方法进行了 一个HomePageModel的实例化。P层 和 M层之间,为什么不采用V-P层的接口形式呢?
- 出于P 层 会出现 一对多个 Model的情况。使用接口的形式会有比较大的限制。
- 出于dart 中 Future,Streams是天然的函数式编程方式,可以轻松的解决回调的情况。
出于这两个考虑,直接实例化Model更合理。
下面是具体的代码:
BaseService :
这个基类很简单,主要是针对网络层面的基类,在构造函数中,进行实例化了一个http的工具类。(后面的文章 httpUtil的分拆)
class BaseService {
static const String TAG = "Xuan_service";
HttpUtil httpUtil;
BaseService() : httpUtil = HttpUtil();
}
HomePageContract
在原来 IHomeView,IHomerPresenter 的基础上,添加 IHomePageModelService。提供给P层调用
abstract class IHomeView extends IView{
updateView();
}
abstract class IHomerPresenter extends IPresenter{
getData();
}
abstract class IHomePageModelService{
Future getHomePageInfo(String userName, int age);
Future setARequest();
Future setBRequest ();
}
HomPageModel
HomPageMode l继承 BaseService, 实现IHomePageModelService接口。
IHomePageModelService 提供了三个接口给P层调用。
对三个接口进行了一一实现。都是通过httpUtil 进行了网络数据的获取。
返回的是 Future 。这样P层可以直接拿到 Future对象,以及数据结果。不需要通过接口回调到P层。
Future 其实典型的函数式编程,和Android 的RxJava 比较像。
class HomePageModel extends BaseService implements IHomePageModelService {
HomePageReq getRequestData() {
return new HomePageReq(deviceId: "dd"
, userData: new UserData(name: "jack",age: 16));
}
@override
Future getHomePageInfo(String userName, int age) {
return httpUtil.post("getServerTimestampdd", getRequestData())
.then((resp) {
//这里可以做想要的转换,也可以什么都不做
HomePageResp result = new HomePageResp.fromJson(resp);
return result;
});
}
///多个请求,异步进行,最后进行统一then处理,结果是顺序的,但是获取是异步的。并行关系
@override
Future setARequest() {
Future future1 = httpUtil.post("getServerTimestamp",getRequestData())
.then((dynamic resp){
print("1" + resp);
return resp;
});
Future future2 = httpUtil.post("getServerTimestamp", getRequestData())
.then((dynamic resp) {
//这里可以做一层你想要的转换
print("2" +resp);
return resp;
});
return Future.wait([future1,future2])
.then((List t){
print(t);
return "test";
}).catchError((e){
print(e);
});
}
///多个请求,一个接一个, 串联关系
@override
Future setBRequest() async {
var data1 = await httpUtil.post("getServerTimestamp","");
var data2 = await httpUtil.post("getServerTimestamp", data1);
return data2;
}
}
小结
这个是简单的 MVP 结构。V-P层通过接口相互调用,P 层直接调用M层实体,通过Future进行响应结果。P可以使用多个Model的相关调用。
上面的HomePageModel 中其实还有其他的封装, 例如 HttpUtil 是对 dio 进行了一个简单的封装。而使用json转对象,利用了json_serializable。这个后面在单独记录下来。