Flutter的MVP开发框架搭建

写给读者

本人是一名安卓开发工程师(兼职IOS),最近学习了一下Flutter,整理了一套MVP开发框架。该框架我是基于Android的mvp思想编写的,还没在正式项目上使用,供大家参考。

项目介绍

包含mvp基础类,网络层封装(RxDart+Dio),数据解析等相关类的封装。

项目结构

image.png

common

  • application.dart 项目全局属性
  • base 项目基类
  • constants 常量
  • network 网络封装库
  • route 路由管理
  • utils 工具类
  • widget 常用控件封装

contract

页面的view层接口和presenter层接口定义

main.dart

项目启动类

代码

application.dart 项目全局属性

class Application {
  //路由管理
  static Router router;
  //依赖注入,全局单例
  static GetIt getIt = GetIt.instance;
  //全局用户信息
  static UserEntity userEntity;

  static SharedPreferences sp;

  static initSp() async {
    sp = await SharedPreferences.getInstance();
  }

  /// 是否登录
  static bool isLogin() {
    return userEntity != null;
  }

  static initScreenUtil(BuildContext context){
//    ScreenUtil.init(context, width: 720, height: 1280);
//    print('设置像素密度:${ScreenUtil.pixelRatio}');
//    print('设置的高度:${ScreenUtil.screenHeight}');
//    print('设置的宽度:${ScreenUtil.screenWidth}');
  }

  /// 依赖注入,全局单例对象
  static setup(){
    getIt.registerSingleton(new DioRequest());
    getIt.registerSingleton(new UserModel());
  }
}

【Model层相关类】

base_entity.dart Bean基类

class BaseEntity {

}

base_model.dart 发送网络请求(待完善)

class BaseModel {

  //接口请求操作类-全局单例
  DioRequest dioRequest = GetIt.instance();
  //请求列表接口,每页数量
  int pageCount = 10;
  //参数
  Map params = new Map();

  void sendRequest(Stream stream, RxObserver observer) {
    //发送请求
    observer.requestHttp(stream);
  }
}

dio_request.dart 封装的网络请求,结合了RxDart+Dio

class DioRequest {

  /// http request methods
  static const String GET = 'GET';
  static const String POST = 'POST';
  static const String PUT = 'PUT';
  static const String PATCH = 'PATCH';
  static const String DELETE = 'DELETE';

  static DioFactory _dioFactory = DioFactory.instance;

  Future request(String action, String url, {Map params, bool isJson}) async {
    var formData;

    if (!isJson) {
      formData = params != null ? FormData.fromMap(params) : null;
    } else {
      formData = params;
    }

    // 获取本地token,添加请求头
    if(Application.userEntity != null){
      UserEntity user = Application.userEntity;

      /// 动态添加headers
      Map headers = new Map();
      //token
      headers['authorization'] = '${user.userId}_${user.token}';
      //时间戳
      headers['timestamp'] = Util.currentTimeMillis().toString();
      //版本号
      headers['version'] = '2.0';
      _dioFactory.dio.options.headers.addAll(headers);
    } else {
      _dioFactory.dio.options.headers.remove('authorization');
    }
    Response response;

    try {
      switch (action) {
        case GET:
          response =
          await _dioFactory.dio.get(url, queryParameters: params);
          break;
        case POST:
          response = await _dioFactory.dio.post(url, data: formData);
          break;
      }
    } on DioError catch (error) {
      print('请求出错:' + error.toString());

      // 请求错误处理
      Response errorResponse;
      if (error.response != null) {
        errorResponse = error.response;
      } else {
        errorResponse = new Response(statusCode: ResultCode.NO_NETWORK);
      }
      // 请求超时
      if (error.type == DioErrorType.CONNECT_TIMEOUT) {
        errorResponse.statusCode = ResultCode.CONNECT_TIMEOUT;
      }
      // 一般服务器错误
      else if (error.type == DioErrorType.RECEIVE_TIMEOUT) {
        errorResponse.statusCode = ResultCode.RECEIVE_TIMEOUT;
      }

      throw CommonException(errorMsg: "网络异常");
    }


    return response.data;
  }

  Stream _get(String url, {Map params}) =>
      Stream.fromFuture(request(GET, url, params: params)).asBroadcastStream();

  Stream _post(String url, Map params, bool isJson) {
    return Stream.fromFuture(request(POST, url, params: params, isJson: isJson)).asBroadcastStream();
  }


  Stream handlerGet(String requestUrl, {Map params}) {
    return _get(requestUrl);
  }

  Stream handlerFormPost(String requestUrl, {Map params}) {
    return _post(requestUrl, params, false);
  }

  Stream handlerJsonPost(String requestUrl, {Map params}) {
    return _post(requestUrl, params, true);
  }
}

rx_observer.dart 订阅网络请求,处理请求开始与结束的统一逻辑

typedef void OnSuccess(dynamic data);

typedef void OnFailure(String error);

class RxObserver {

  //页面引用对象(显示进度框、提示语)
  BaseView view;
  //提示语
  String msg;
  //是否显示进度框
  bool isShowDialog;
  //成功回调
  OnSuccess onSuccess;
  //失败回调
  OnFailure onFailure;

  RxObserver(this.view, {this.onSuccess, this.onFailure, this.isShowDialog = true});


  ///http网络请求 [request] 接收 Stream 类型
  void requestHttp(Stream request) {

    request.doOnListen(() {

      //开始网络请求,根据提示语显示弹框
      if (view != null && isShowDialog) {
        view.showLoading(msg: msg);
      }

    }).listen((data) {
      //请求成功,返回结果,关闭弹框
      if (view != null && isShowDialog) {
        view.closeLoading();
      }

      //请求成功 获取数据data, data是返回结果json
      BaseResponse response = BaseResponse.fromJson(data);

      if (response.resultCode != 100) {
        handlerError(response.resultMsg);
      } else {
        onSuccess(response.getData());
      }

    }, onError: (error) {
      if (view != null && isShowDialog) {
        view.closeLoading();
      }

      //请求失败
      DioError e = error;
      handlerError(e.message);
    }, onDone: () {
      //执行结束

    });

//    subject.cancel();

  }

  void handlerError(String error) {
    if (view != null && isShowDialog) {
      view.showError(errorMsg: error);
    }
    //请求失败
    onFailure(error);
  }
}

base_response.dart 接口返回结果基类

class BaseResponse {
  dynamic data;
  bool success;
  int resultCode;
  String resultMsg;

  BaseResponse({this.data, this.success, this.resultCode, this.resultMsg});

  BaseResponse.fromJson(Map json) {
    data = json['data'];
    success = json['success'];
    resultCode = json['resultCode'];
    resultMsg = json['resultMsg'];
  }

  /// 获取results对象
  T getData() {
    return EntityFactory.generateOBJ(data); //使用EntityFactory解析对象
  }

  /// 获取results数组
  List getList() {
    var newList = new List();
    if (data != null) {
      data.forEach((v) { //拼装List
        newList.add(EntityFactory.generateOBJ(v));//使用EntityFactory解析对象
      });
    }
    return newList;
  }

  Map toJson() {
    final Map data = new Map();
    data['data'] = this.data;
    data['success'] = this.success;
    data['resultCode'] = this.resultCode;
    data['resultMsg'] = this.resultMsg;
    return data;
  }
}

【View层相关】

base_state.dart 页面基类(等同android/ios的BaseActivity/BaseViewController)

/// state基类
abstract class BaseState

extends State with AutomaticKeepAliveClientMixin implements BaseView { P mPresenter; //是否初始化 bool _isPrepared = false; //请求dialog LoadingDialog dialog; @override void initState() { mPresenter = createPresenter(); _attachView(); super.initState(); } ///构建页面 @override Widget build(BuildContext context) { super.build(context); // ScreenUtil.init(context, width: 750, height: 1334); if (!_isPrepared) { Timer.run(() => preparePage()); _isPrepared = true; } return Scaffold( appBar: buildAppBar(), body: buildPageLayout(), ); } Widget buildAppBar(); Widget buildPageLayout(); @mustCallSuper @override void dispose() { super.dispose(); _detachView(); } P createPresenter(); /// 初始化一次 =》 用于 presenter 请求网络数据后调用 showDialog 拿不到合适的 context 报错 void preparePage(); @override void reload() {} @override void renderPage(Object o) {} @override void showDisConnect() {} @override void showError({String errorMsg}) { if (errorMsg != null) { ToastUtil.showToast(errorMsg); } } @override void showLoading({String msg}) { /// 把 dialog 的 show 从 普通页面里分离 if (dialog == null) { dialog = LoadingDialog( text: msg ?? '加载中', ); } showDialog( context: context, barrierDismissible: true, builder: (BuildContext context) { return dialog; }); } @override void closeLoading() { /// 必须和 showLoading 方法配对使用 ,避免 pop 当前页面 if (dialog != null) { Navigator.pop(context); dialog = null; } } void _attachView(){ if(null != mPresenter){ mPresenter.attachView(this); } } void _detachView(){ if(null != mPresenter){ mPresenter.detachView(); } } //不会被销毁,占内存中 @override bool get wantKeepAlive => true; }

base_view.dart

abstract class BaseView {

  void showLoading({String msg});

  void closeLoading();

  void renderPage(Object object);

  void reload();

  void showError({String errorMsg});

  void showDisConnect();

}

【presenter相关】

base_presenter.dart

class BasePresenter {
  V view;


  /// 绑定视图,页面创建时调用
  void attachView(V view) {
    this.view = view;
  }

  /// 解绑视图,页面销毁时调用
  void detachView() {
    if (null != view) {
      this.view = null;
    }
  }
}

以上是项目结构及相关重要的几个类介绍,源码在文章最后,下面看下实际使用:

main.dart 项目启动类

void main() {
  //初始化路由管理
  Router router = new Router();
  Routes.configureRoutes(router);
  Application.router = router;
  //注册
  Application.setup();

  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        brightness: Brightness.light,
        primaryColor: Color(0xFF00A0E9),
        splashColor: Colors.transparent,
        backgroundColor: Color(0xFFFFFFFF),
      ),
      home: SplashPage(),
      onGenerateRoute: Application.router.generator,
//      routes: {
//        "/test" : (context) => new TestPage(),
//      },
    );
  }
}

以登录页为例

【model层】

user_entity.dart 登录信息bean

class UserEntity {
    var appId;
    var redisKey;
    var userId;
    var token;

    UserEntity({this.appId, this.redisKey, this.userId, this.token});

    UserEntity.fromJson(Map json) {
        appId = json['appId'];
        redisKey = json['redisKey'];
        userId = json['userId'];
        token = json['token'];
    }

    Map toJson() {
        final Map data = new Map();
        data['appId'] = this.appId;
        data['redisKey'] = this.redisKey;
        data['userId'] = this.userId;
        data['token'] = this.token;
        return data;
    }
}

user_model.dart 用户model操作类

class UserModel extends BaseModel {


  /// 登录
  void login(String account, String password, RxObserver observer){
    password = Util.generateMd5(password);

    params.clear();
    params['username'] = account;
    params['password'] = password;

    Stream stream = dioRequest.handlerFormPost(Url.login, params: params);
    sendRequest(stream, observer);
  }
}

【view层】

page_login.dart 登录页面

class LoginPage extends StatefulWidget {
  @override
  LoginPageState createState() => LoginPageState();
}

class LoginPageState extends BaseState implements ILoginView{

  bool isPasswordLogin = false; //是否密码登录,默认验证码登录

  String _phoneNumber;
  String _password;


  @override
  void initState() {
    super.initState();
  }

  @override
  Widget buildAppBar() {

    return AppBar(
      backgroundColor: Colors.white,
      iconTheme: IconThemeData(color: Colors.black),
      elevation: 0,
    );
  }

  @override
  Widget buildPageLayout() {

    return Container(
      color: Colors.white,
      height: double.infinity,
      padding: EdgeInsets.only(left: 20, right: 20),
      child: Stack(
        alignment: AlignmentDirectional.center,
        children: [

          Positioned(
            top: 0,
            left: 0,
            bottom: 0,
            right: 0,
            child: SingleChildScrollView(

              child: Column(
                mainAxisSize: MainAxisSize.max,
                crossAxisAlignment: CrossAxisAlignment.start, //左侧对齐
                children: [

                  //登录图标
                  Row(
                    children: [
                      Image.asset("images/login_logo.png", width: UIUtil.getWidth(61), height: UIUtil.getHeight(61),),
                      UIUtil.createText("登录", 24, AppColor.black4, isBold: true, margin: EdgeInsets.only(left: 7)),
                      InkWell(
                        child: UIUtil.createText(isPasswordLogin ? "验证码登录" : "密码登录",
                            12, AppColor.grayText, isBold: true, margin: EdgeInsets.only(left: 10)),
                        onTap: (){
                          setState(() {
                            isPasswordLogin = !isPasswordLogin;
                          });
                        },
                      )

                    ],
                  ),

                  //提示
                  UIUtil.createText("未注册手机号验证后即完成注册", 12, AppColor.grayText, margin: EdgeInsets.only(top: 56, bottom: 10)),

                  //手机号
                  ITextField(
                    keyboardType: ITextInputType.phone,
                    hintText: '请输入手机号',
                    hintStyle: TextStyle(color: AppColor.grayText),
                    textStyle: TextStyle(color: AppColor.black4),
                    fieldCallBack: (content) {
                      _phoneNumber = content;
                      print(_phoneNumber);
                    },
                  ),
                  //密码
                  Container(
                    child: Stack(
                      children: [
                        ITextField(
                          margin: EdgeInsets.only(top: 20),
                          keyboardType: ITextInputType.password,
                          hintText: isPasswordLogin ? '请输入密码' : '请输入验证码',
                          hintStyle: TextStyle(color: AppColor.grayText),
                          textStyle: TextStyle(color: AppColor.black4),
                          fieldCallBack: (content) {
                            _password = content;
                            print(_password);
                          },
                        ),
                        Positioned(
                          bottom: 0,
                          right: 10,
                          child: CustomCountdown((){
                            print("请求验证码");
                          }),
                        )
                      ],
                    ),
                  ),

                  //登录
                  Container(
                    margin: EdgeInsets.only(top: 120),
                    width: double.infinity,
                    height: UIUtil.getHeight(50),
                    child: RaisedButton(
                      child: Text("登录", style: TextStyle(fontSize: UIUtil.getSp(16)),),
                      textColor: AppColor.black4,
                      color: AppColor.theme,
                      shape:RoundedRectangleBorder(borderRadius: BorderRadius.circular(25.0)),
                      onPressed: _login,
                    ),
                  ),

                  //注册视图
                  isPasswordLogin ?
                  Container(
                    margin: EdgeInsets.only(top: 10, left: 10, bottom: UIUtil.getHeight(82)),
                    child: Row(
                      children: [
                        UIUtil.createText("没有账号,", 12, AppColor.black6, isBold: true),
                        InkWell(
                          child: UIUtil.createText("去注册", 12, AppColor.theme, isBold: true),
                          onTap: (){
                            print("去注册");
                          },
                        ),
                        Spacer(
                          flex: 1,
                        ),
                        InkWell(
                          child: UIUtil.createText("忘记密码?", 12, AppColor.grayText, isBold: true, textAlign: TextAlign.end),
                          onTap: (){
                            print("忘记密码");
                          },
                        ),
                      ],
                    ),
                  ) : Text(""),
                ],
              ),
            ),
          ),

          //底部按钮
          Positioned(
//            width: double.infinity,
            bottom: 30,
            child: Row(

              children: [
                Text.rich(TextSpan(
                    children: [
                      TextSpan(
                        text: "《服务协议》",
                        style: TextStyle(
                            color: AppColor.theme
                        ),
//                      recognizer: _tapRecognizer
                      ),
                      TextSpan(
                        text: "和",
                        style: TextStyle(
                            color: AppColor.grayText
                        ),
                      ),
                      TextSpan(
                        text: "《隐私政策》",
                        style: TextStyle(
                            color: AppColor.theme
                        ),
//                      recognizer: _tapRecognizer
                      ),
                    ]
                )),
              ],
            ),
          )
        ],
      ),
    );
  }

  void _login() {
    mPresenter.login();
  }

  @override
  void preparePage() {
    // TODO: implement preparePage
  }

  @override
  String getAccount() {
    return _phoneNumber;
  }

  @override
  String getPassword() {
    return _password;
  }

  @override
  LoginPresenter createPresenter() {
    return LoginPresenter();
  }

  @override
  void loginSuccess(UserEntity user) {
    Application.userEntity = user;
    //存储用户信息
    String userJson = Util.json2String(user.toJson());
    SpUtil.setString(SPKey.USER, userJson);
    RouteUtil.goMainPage(context, replace: true);
  }

}

login_page_contract.dart 登录页面的view接口和presenter接口定义

abstract class ILoginView extends BaseView {
  /// 获取用户输入账号
  String getAccount();

  /// 获取用户输入密码
  String getPassword();

  /// 登录成功
  void loginSuccess(UserEntity entity);

}

abstract class ILoginPresenter  extends BasePresenter {

  /// 登录
  void login();

  /// 获取验证码
  void getPhoneCode();

}

【presenter层】

login_presenter.dart

class LoginPresenter extends ILoginPresenter {

  @override
  void login() {
    // TODO: implement login
    String account = view.getAccount();
    String password = view.getPassword();

    if (account == null || account.length == 0) {
      view.showError(errorMsg: '请输入手机号');
      return;
    }
    if (password == null || password.length == 0) {
      view.showError(errorMsg: '请输入密码');
      return;
    }

    Application.getIt.get().login(account, password, new RxObserver(view,
      onSuccess: (data) {
        print(data);
        view.loginSuccess(data);
      },
      onFailure: (error) {

      }
    ));
  }

  @override
  void getPhoneCode() {
    // TODO: implement getPhoneCode
  }

}

补充介绍 base_list_page.dart 列表基类

开发中,经常有列表页面,所有会存在一些公共逻辑,比如下拉刷新,加载更多,空视图、失败视图等。base_list_page可以省去一些公共代码,减少开发工作量。

/// 列表页面的state基类
abstract class BaseListPageState

extends BaseState implements BaseListView { List list = []; RefreshController _refreshController = RefreshController(initialRefresh: false); int page = 1; int total; bool isFailure = false; void _onRefresh() async { page = 1; requestListData(); } void _onLoading() async { requestListData(); } @override int getPage() { return page; } @override void listFailure() { if (page == 1) { isFailure = true; _refreshController.refreshCompleted(); } else { _refreshController.loadFailed(); } setState(() { }); } @override void listSuccess(ListEntity result) { total = result.count; if (page == 1) { isFailure = false; setEnableLoadMore(true); list.clear(); list.addAll(result.getList()); _refreshController.refreshCompleted(); } else { list.addAll(result.getList()); _refreshController.loadComplete(); } page++; if (list == null || list.length == 0) { //列表为空 return; } if (list.length >= total) {//没有更多数据了 _refreshController.loadNoData(); } setState(() { }); } //是否开启下拉刷新 bool enableRefresh = true; //是否开启上拉加载 bool enableLoadMore = true; /// 构建一个带刷新的列表视图 Widget buildListView() { return SmartRefresher ( enablePullDown: enableRefresh, enablePullUp: enableLoadMore, // WaterDropHeader、ClassicHeader、CustomHeader、LinkHeader、MaterialClassicHeader、WaterDropMaterialHeader header: ClassicHeader( height: 45.0, releaseText: '松开手刷新', refreshingText: '刷新中', completeText: '刷新完成', failedText: '刷新失败', idleText: '下拉刷新', ), // ClassicFooter、CustomFooter、LinkFooter、LoadIndicator footer: CustomFooter( builder: (BuildContext context, LoadStatus mode) { Widget body; if (mode == LoadStatus.idle) { body = Text("上拉加载更多"); } else if (mode == LoadStatus.loading) { body = CupertinoActivityIndicator(); } else if (mode == LoadStatus.failed) { body = Text("加载失败,点击重试"); } else { body = Text("已经到底了"); } return Container( height: 55.0, child: Center(child: body), ); }, ), controller: _refreshController, onRefresh: _onRefresh, onLoading: _onLoading, child: contentView(), ); } ///有返回列表视图。无数据,返回空视图 Widget contentView() { if (page == 1 && isFailure) { return HintWidget(HintWidget.ERROR, function: () { /// 列表请求失败时,重新请求 requestListData(); }); } if (list == null || list.length == 0) { //第一次返回加载中,total有值时,list=0返回空视图 return HintWidget(total != null ? HintWidget.EMPTY : HintWidget.LOADING); } else { //返回列表视图 return ListView.builder( itemBuilder: (BuildContext context, int index){ return GestureDetector( onTap: () { //item Click onItemClick(context, index); }, child: buildItem(context, index), ); }, itemCount: list.length, // itemExtent: 100.0, // cacheExtent: 10, ); } } //构建列表行布局 Widget buildItem(BuildContext context, int index); //列表行点击事件 void onItemClick(BuildContext context, int index); //请求列表数据 void requestListData(); //是否开启加载更多 void setEnableLoadMore(enable) { enableLoadMore = enable; } //是否开启下拉刷新 void setEnableRefresh(enable) { enableRefresh = enable; } // don't forget to dispose refreshController @override void dispose() { _refreshController.dispose(); super.dispose(); } }

base_list_view.dart

abstract class BaseListView extends BaseView {

  //加载成功
  void listSuccess(ListEntity result);
  //加载失败
  void listFailure();
  //获取当前页数
  int getPage();

}

使用示例

/// 车辆列表
class CarListPage extends StatefulWidget {
  @override
  _CarListPageState createState() => _CarListPageState();
}

/// 指定泛型 ,最后一个类型为列表对应的实体类
class _CarListPageState extends BaseListPageState implements ICarListView{

  @override
  Widget buildAppBar() {
    // TODO: implement buildAppBar
    return new AppBar(title: Text("列表页"),);
  }

  @override
  Widget buildItem(BuildContext context, int index) {
    // TODO: implement buildItem
    return OnlineCarItem(list[index], (carEntity){

      if (Application.isLogin()) {
        //根据收藏状态,调用不同接口
        if (Util.equals(carEntity.isFollow, "1")) {
          mPresenter.cancelCollectionCar(carEntity);
        } else {
          mPresenter.collectionCar(carEntity);
        }
      } else {
        RouteUtil.goLoginPage(context);
      }

    });
  }

  @override
  Widget buildPageLayout() {
    // TODO: implement buildPageLayout
    return buildListView();
  }

  @override
  CarListPresenter createPresenter() {
    // TODO: implement createPresenter
    return new CarListPresenter();
  }

  @override
  void onItemClick(BuildContext context, int index) {
    // TODO: implement onItemClick
    CarEntity carEntity = list[index];
    print("点击:$carEntity");
    RouteUtil.goDetailPage(context, carEntity.id.toString());
  }

  @override
  void preparePage() {
    // TODO: implement preparePage
    requestListData();
  }

  @override
  void requestListData() {
    // TODO: implement requestListData
    mPresenter.getList();
  }

  @override
  void collectionSuccess() {
    // TODO: implement likeSuccess
    // 操作成功,刷新列表
    setState(() {

    });
  }

  @override
  void cancelCollectionSuccess() {
    // TODO: implement cancelCollectionSuccess
    // 操作成功,刷新列表
    setState(() {

    });
  }

}

car_list_page_contract.dart

abstract class ICarListView extends BaseListView {

  /// 收藏成功
  void collectionSuccess();
  /// 取消收藏成功
  void cancelCollectionSuccess();
}

abstract class IListPresenter  extends BasePresenter {

  /// 获取列表
  void getList();

  /// 收藏车辆
  void collectionCar(CarEntity carEntity);

  /// 取消收藏车辆
  void cancelCollectionCar(CarEntity carEntity);
}

car_list_presenter.dart

class CarListPresenter extends IListPresenter {
  @override
  void getList() {
    Application.getIt.get().getCarList(view.getPage(), new RxObserver(view,
      onSuccess: (data) {
        view.listSuccess(data);
      },
      onFailure: (error) {
        view.listFailure();
      }
    ));
  }

  @override
  void collectionCar(CarEntity carEntity) {
    Application.getIt.get().collection(carEntity.id.toString(), new RxObserver(view,
        onSuccess: (data) {
          //车辆关注状态更改
          carEntity.isFollow = carEntity.isFollow.toString().endsWith("1") ? "2" : "1";
          view.collectionSuccess();
        },
        onFailure: (error) {

        }
    ));
  }

  @override
  void cancelCollectionCar(CarEntity carEntity) {
    Application.getIt.get().cancelCollection(carEntity.id.toString(), new RxObserver(view,
        onSuccess: (data) {
          //车辆关注状态更改
          carEntity.isFollow = carEntity.isFollow.toString().endsWith("1") ? "2" : "1";
          view.cancelCollectionSuccess();
        },
        onFailure: (error) {

        }
    ));
  }
}

【用到的一些框架】

页面路由管理

https://github.com/theyakka/fluro

简单数据存储

https://pub.dev/packages/shared_preferences

网络请求

https://github.com/flutterchina/dio

Rx

https://pub.dev/packages/rxdart

依赖注入(单例)

https://pub.flutter-io.cn/packages/get_it

下拉刷新 加载

https://pub.dev/packages/pull_to_refresh

图片加载

https://pub.dev/packages/cached_network_image

【最后】

现阶段对Flutter的很多地方理解不够,能力有限,如有不合适需要改善的地方,可以多提宝贵意见!

【项目地址】

https://github.com/zhengqiyao93/Flutter_Mvp

你可能感兴趣的:(Flutter的MVP开发框架搭建)