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<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:
- 泛型接收View(State)層的接口
- 構造方法接收 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,有幾個方法需要在這裏複寫:
- getPresenter 獲取Presenter 實體
- initViewState 對應 State的initState
- 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 接口。
這裏比較簡單:
- 構造方法,傳入View 接口,通過IView 就可以調用V層的UI了。 在構造時候,同時進行了M層的實例化
- 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層的接口形式呢?
- 出於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<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。這個後面在單獨記錄下來。