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。这个后面在单独记录下来。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容