聊聊 Flutter 开发 mvp 和 网络框架 使用及简单封装

github地址

背景:flutter 跨平台开发吸引人,想试试

web -> service -> dao
view -> presenter -> model
mvp 此类设计可以把工程易变的和不容易变的分离,是为解耦。关于为什么要解耦,如何解耦,什么是解耦 ... 我们暂且不聊~
既然要开发 flutter 工程,我们必然要做一些基础工作。比如 mvp,网络请求,工具类,基础UI 等等的封装和抽取


common

介绍一下上图:
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 不让用反射,应该也有其他的实现动态代理实现方式吧😄

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

推荐阅读更多精彩内容