github位址
背景:flutter 跨平台開發吸引人,想試試
web -> service -> dao
view -> presenter -> model
mvp 此類設計可以把工程易變的和不容易變的分離,是為解耦。關于為什麼要解耦,如何解耦,什麼是解耦 … 我們暫且不聊~
既然要開發 flutter 工程,我們必然要做一些基礎工作。比如 mvp,網絡請求,工具類,基礎UI 等等的封裝和抽取
介紹一下上圖:
1:和業務線無關的 base/core/utils/mvp/network
2:和技術選型無關的 mvp/core 也就是 mvp 和 網絡封裝(core 通過擴充卡可适配各種網絡架構)
今天,我們隻看 flutter 中 mvp 和 網絡架構的封裝及其使用:
1:mvp
基于 google Android Architecture =>https://github.com/googlesamples/android-architecture
mvp 實作: 遵守 contract 面向接口程式設計思想
HomeContract.dart
class View extends ILoadingView{
}
class Presenter extends IPresenter{
void getBannerList(){}
}
HomePresenter.dart 負責業務相關實作
class HomePresenter extends BasePresenter<View> implements Presenter {
HomePresenter(View view) : super(view);
@override
void start() {}
@override
void getBannerList() {
view.showLoading();
// 方式一
HttpProxy.getBannerList((int state, dynamic data) {
if (state == HState.success) {
view.closeLoading();
List<Banner> bannerList = new GetBannerListJsonParser().parse(data);
view.renderPage(bannerList);
} else {
view.closeLoading();
view.showError(data.toString());
}
});
// 方式二
// HttpProxy.getBannerList().then((Response res) {
// view.closeLoading();
// List<Banner> bannerList =
// new GetBannerListJsonParser().parse(res.data);
// view.renderPage(bannerList);
// }).catchError((e) {
// view.closeLoading();
// view.showError(e.toString());
// });
}
}
方式1和方式2的兩種網絡請求我們一會兒再看,接下來我們看 ?
mvp 封裝:
最頂層基類,我們遵守可擴充原則
IPresenter.dart 和 IView.dart
class IPresenter{}
class IView{}
對于app常見頁面場景 ILoadingView.dart 和 ILoadingListView.dart
class ILoadingView extends IView{
void showLoading(){}
void closeLoading(){}
void renderPage(Object o){}
void reload(){}
void showError(String msg){}
void showDisconnect(){}
}
class ILoadingListView extends ILoadingView{
void showEmpty(){}
}
關于命名,dart 沒有 interface 我就以 I 開頭了,至于抽象類建議 Abs 開頭
如何把 view 和 presenter 關聯呢,看 BasePresenter.dart
abstract class BasePresenter<T> {
T view;
BasePresenter(this.view);
void start();
void stop() {
this.view = null;
}
}
關于這樣寫,也是受 google 的那些 sample 影響,至少有一個 start () 方法,可以用可以不用,個人習慣,不過還是建議,有構造就有析構 有始有終,至于指定泛型,好處呢,編譯前裡可以找到 view 的方法并且省去一些 view 綁定 presenter 代碼的編寫。dartVm沒有泛型插除,帶來自由的同時,也帶來了危險,不過 dynamic 的類型真的很好。
2:網絡請求二次封裝
core 是存粹的,不應該引入任何網絡請求的庫,這樣才能做到通用性,通過 Adapter 來做不同網絡架構的适配
core 裡 HttpUtils 的使用:初始化擴充卡
main.dart 一些個人想法,所有 widget 組成的 page 都應有 state ,stateless 可以做一些自定義基礎控件和無互動的UI展示
void main() {
ThirdLibsManager.get().setup();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
State<StatefulWidget> createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
// ...
}
@override
void dispose() {
super.dispose();
// 這個回調放這可能不對,感覺要用 channel 建立 dart 和 native(and/ios) 通道
ThirdLibsManager.get().release();
}
}
class TDelegate{
void setup(){}
void release(){}
}
class ThirdLibsManager implements TDelegate{
static final ThirdLibsManager _instance = new ThirdLibsManager._internal();
static ThirdLibsManager get() => _instance;
factory ThirdLibsManager() => _instance;
ThirdLibsManager._internal();
@override
void release() {}
@override
void setup() {
// 設定擴充卡
HttpUtils.get().setAdapter(DioAdapter());
Log.setEnable(true);
}
}
設定擴充卡以後的工作就是之前我們看到 HomePresenter.dart 裡的那樣了
封裝:
1:interface 一些接口和協定,可以看出 dart 語言靈活的魅力,一千個讀者一千個哈姆雷特,總有一種寫法适合你;
2:utils 一個入口;
3:ctx 一個抽象的全局的結構體;
HInterface.dart
import 'dart:async';
import 'RequestCtx.dart';
abstract class JsonParser<T> {
T parse(String jsonStr);
}
abstract class HAdapter {
Future<dynamic> request(RequestCtx ctx);
}
// a transformer
typedef transformer = String Function(String original);
// a callback
typedef dataCallback = Function(int state, dynamic data);
class HState{
static final int success = 1;//成功
static final int fail = 0;//失敗
static final int intercept = -1;//中斷
}
RequestCtx.dart 包含了網絡請求和響應的所有必要的結構組成
import 'dart:io';
import 'HInterface.dart';
class HConstants {
static final String get = "get";
static final String post = "post";
static final int timeout = 15000;
}
class RequestCtx {
String _url;
String _method;
int _timeout;
ContentType _contentType;
dynamic _responseType;
Map<String, dynamic> _paramMap;
Map<String, dynamic> _headerMap;
Map<String, dynamic> _bodyMap;
int _retryCount;
dynamic _transformer;
List<dynamic> _interceptors;
dataCallback _callback;
// ... 太多了,就不寫了,可以 看github ?article start part
}
上面看到有2種網絡請求的書寫方式呢?我們看 HttpUtils 入口和 Adatper 接口的實作類:
HttpUtils.dart 某些人寫的網絡架構做了更深的 future 的封裝,我們就不需要 callback,沒做的,我們要在 adapter 的實作類裡手動回調,這也是 callback 函數的意義。
class HttpUtils{
HAdapter _adapter;
// 省略 單列模式 的固定書寫
setAdapter(HAdapter adapter){
this._adapter = adapter;
}
Future<dynamic> req(String url, {String method,int timeout,
Map<String, dynamic> header,
Map<String, dynamic> params,
Map<String, dynamic> body,
Transformer transformer,
List<Interceptor> interceptors,
dataCallback callback
}) {
assert(_adapter!=null);//沒有做adapter的實作是無法去請求的 asset 很強大
try {
RequestCtx ctx = new Builder()
.setUrl(url)
.setMethod(method)
.setHeaderMap(header)
.setTimeout(timeout)
.setParams(params)
.setResponseType(ResponseType.plain)
.setBodyMap(body)
.setTransformer(transformer)
.setInterceptors(interceptors)
.setDataCallback(callback)
.build();
return _adapter.request(ctx);
} catch (e) {
print(e.toString());
rethrow;
}
}
String wrapUrlByParams(String url,Map<String, dynamic> params){
String ret = url;
if (params != null && params is Map && params.isNotEmpty) {
StringBuffer sb = new StringBuffer("?");
params.forEach((key, value) {
sb.write("$key" + "=" + "$value" + "&");
});
String paramStr = sb.toString();
paramStr = paramStr.substring(0, paramStr.length - 1);
ret += paramStr;
}
return ret;
}
}
DioAdapter.dart 這裡先寫一個 dio 的擴充卡,dynamic 的好處大家可以體會到,要實作其他的網絡架構可以自己适配:
class DioAdapter implements HAdapter{
Dio _dio;
DioAdapter() {
_dio = new Dio();
}
@override
Future<Response<dynamic>> request(RequestCtx ctx) async {
Future<Response<dynamic>> response;
_dio.options = new BaseOptions(
connectTimeout: ctx.timeout == null ? HConstants.timeout : ctx.timeout,
receiveTimeout: ctx.timeout == null ? HConstants.timeout : ctx.timeout,
headers: ctx.headerMap==null?{HttpHeaders.userAgentHeader: "dio-2.1.0"}:ctx.headerMap,
contentType: ctx.contentType == null ? ContentType.json : ctx.contentType,
responseType: ctx.responseType == null ? ResponseType.json : ctx.responseType,
validateStatus: (status) {
return status >= 200 && status < 300 || status == 304;
}
);
if (ctx.transformer != null) {
_dio.transformer = ctx.transformer;
}
if (ctx.interceptors != null && ctx.interceptors.isNotEmpty) {
for (var value in ctx.interceptors) {
_dio.interceptors.add(value);
}
}
String url = HttpUtils.get().wrapUrlByParams(ctx.url, ctx.paramMap);
switch (ctx.method) {
case "get":
response = _dio.get(url);
break;
case "post":
response = _dio.post(url, data: ctx.bodyMap);
break;
default:
response = _dio.get(url);
}
if(ctx.callback!=null){
response.then((response){
// can by some response.statusCode to make some regex
ctx.callback(HState.success,response.data);
}).catchError((e){
ctx.callback(HState.fail,e);
});
}
return response;
}
}
感謝?,讀到這裡的大佬,萬分感謝你們:)後期可能會把 Adapter 的形式改成注解@ +動态代理 也就是類似于 Retrofit 那樣的擴充卡。盡管 flutter 不讓用反射,應該也有其他的實作動态代理實作方式吧?