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。這個後面在單獨記錄下來。

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