Flutter MVP实践

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

  1. 对State 的 build 方法进行一个简单封装,子类只要实现 buildViews 即可。
  2. 然后就是 presenter ,通过getPresenter ,让子类进行实例化。
  3. initState 状态也进行简单封装,获取到presenter实体,并赋值给mPresenter。同时抛出一个initViewState给子类去调用。
    View(State)层 就可以通过 mPersenter 调用 P层提供的接口了。
abstract class BaseState<T extends BaseStatefulWidget, E extends IPresenter> extends State<T> {
  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:

  1. 泛型接收View(State)层的接口
  2. 构造方法接收 View (State)实体, 赋值给 view 。方便调用View层接口

ps :其他代码忽略,其实处理M层数据需要的封装

abstract class BasePresenter<T extends IView> 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,有几个方法需要在这里复写:

  1. getPresenter 获取Presenter 实体
  2. initViewState 对应 State的initState
  3. buildViews 对应 State的 build
class _HomePageState extends BaseState<HomePage, IHomerPresenter> 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: <Widget>[
            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 接口。

这里比较简单:

  1. 构造方法,传入View 接口,通过IView 就可以调用V层的UI了。 在构造时候,同时进行了M层的实例化
  2. getData是对接口文件 IHomePresenrer的实现,供View层调用。
class HomePagePresenter extends BasePresenter<IHomeView> 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层的接口形式呢?

  1. 出于P 层 会出现 一对多个 Model的情况。使用接口的形式会有比较大的限制。
  2. 出于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<HomePageResp> getHomePageInfo(String userName, int age);
  Future<dynamic> setARequest();
  Future<dynamic> 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<HomePageResp> getHomePageInfo(String userName, int age) {
    return httpUtil.post("getServerTimestampdd", getRequestData())
        .then((resp) {
          //这里可以做想要的转换,也可以什么都不做
           HomePageResp result = new HomePageResp.fromJson(resp);
           return result;
        });
  }

  ///多个请求,异步进行,最后进行统一then处理,结果是顺序的,但是获取是异步的。并行关系
  @override
  Future<dynamic> 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<dynamic> 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。这个后面在单独记录下来。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章