Flutter MVP

Flutter MVP package

  Some architectures are often used in Android development, from MVC to MVVP, MVVM, etc. These architectures will greatly decouple the functional modules of our code, making our code easier to expand and maintain in the middle and later stages of the project.

  There are also MVC, MVP, MVVM and other architectures in Flutter. In the actual development of Android, the project was also switched from MVC to MVP, forming a set of MVP rapid development framework, and made an AS rapid code generation plug-in. So in the development of Flutter, I also think about whether it can be developed with the MVP architecture and be the same code generation plug-in.

  So here is how to use MVP mode to develop applications in Flutter.

MVC

  When it comes to MVP, you have to mention MVC. Regarding the MVC architecture, you can see the following picture:

 

 

  MVC is the Model View Controller. Simply put, it is to manipulate the data of the model layer through the control of the controller and return it to the view layer for display, as shown in the figure above. When the user initiates an event, the view layer will send instructions to the controller layer, and then the controller will notify the model layer to update the data. After the model layer has updated the data, it will be displayed directly on the view layer. This is the working principle of MVC.

 

  This principle will cause a fatal flaw: when a lot of business logic is written in vidget, widgets act as both the View layer and the Controller layer. Therefore, the coupling is extremely high, and various business logic codes and View codes are mixed together. If you want to modify a requirement, there may be many changes, which is very inconvenient to maintain.

MVP

 

 

  The MVP mode is equivalent to adding a Presenter to the MVC mode to process the model and logic, and to completely separate the View and the Model. The manifestation in the flutter development is that the widget is only used for display interface and interaction, and the widget does not participate in the model structure and logic.

 

  Using the MVP mode will make the code more interfaces, but make the code logic clearer, especially when dealing with complex interfaces and logic, each business can be separated into a Presenter for the same widget, so that the code is clear and logical And easy to expand. Of course, if the business logic itself is relatively simple, the use of MVP mode is not so necessary. So there is no need to use it in order to use it, the specifics still depend on business needs.

  In short: view is UI, model is data processing, and persenter is their link.

potential problem

  1. Model performs asynchronous operations, and when the result is returned to the View through the Presenter, a null pointer exception referenced by the View occurs
  2. Presenter and View hold references to each other to remove memory leaks caused by untimely.

Therefore, when designing the MVP architecture, you need to consider whether the View is empty when the Presenter sends back the View?

When will the Presenter and the View be dereferenced, that is, can the Presenter and the View layer be synchronized in the life cycle?

  Well, having said so much, I personally recommend mvp, mainly because it is relatively simple and easy to use. Let's take a look at how to implement the MVP encapsulation elegantly.

MVP package

Code structure

 

 

 

See the end for the specific code

Code explanation

Model package

/// @desc  基础 model
/// @time 2019-04-22 10:33 am
/// @author Cheney
abstract class IModel {
  ///释放网络请求
  void dispose();
}


import 'package:flutter_mvp/model/i_model.dart';

/// @desc  基础 Model 生成 Tag
/// @time 2019-04-22 12:06 am
/// @author Cheney
abstract class AbstractModel implements IModel {
  String _tag;

  String get tag => _tag;

  AbstractModel() {
    _tag = '${DateTime.now().millisecondsSinceEpoch}';
  }
}

复制代码

The IModel interface has an abstract dispose, which is mainly used to release network requests.

The AbstractModel abstract class implements the IModel interface, and a unique tag is generated in the construction method to cancel network requests.

See the end for the specific code

Present package

import 'package:flutter_mvp/view/i_view.dart';

/// @desc  基础 Presenter
/// @time 2019-04-22 10:30 am
/// @author Cheney
abstract class IPresenter<V extends IView> {
  ///Set or attach the view to this mPresenter
  void attachView(V view);

  ///Will be called if the view has been destroyed . Typically this method will be invoked from
  void detachView();
}


import 'package:flutter_mvp/model/i_model.dart';
import 'package:flutter_mvp/presenter/i_presenter.dart';
import 'package:flutter_mvp/view/i_view.dart';

/// @desc  基础 Presenter,关联 View\Model
/// @time 2019-04-22 10:51 am
/// @author Cheney
abstract class AbstractPresenter<V extends IView, M extends IModel>
    implements IPresenter {
  M _model;
  V _view;

  @override
  void attachView(IView view) {
    this._model = createModel();
    this._view = view;
  }

  @override
  void detachView() {
    if (_view != null) {
      _view = null;
    }
    if (_model != null) {
      _model.dispose();
      _model = null;
    }
  }

  V get view {
    return _view;
  }

//  V get view => _view;

  M get model => _model;

  IModel createModel();
}

复制代码

A generic V is set in the IPresenter interface to inherit IView, V is a view related to the presenter, and there are two abstract methods attachView and detachView.

In the AbstractPresenter abstract class, a generic V inherits IView, and a generic M inherits IModel to implement IPresenter. This class holds a reference to View and a reference to Model. View is bound to attachView, and an abstract method for creating Model objects is generated for subclasses to implement, and View and Model are destroyed in detachView, which solves the above-mentioned mutual holding of references and causes memory leaks.

See the end for the specific code

View package

/// @desc  基础 View
/// @time 2019-04-22 10:29 am
/// @author Cheney
abstract class IView {
  ///开始加载
  void startLoading();

  ///加载成功
  void showLoadSuccess();

  ///加载失败
  void showLoadFailure(String code, String message);

  ///无数据
  void showEmptyData({String emptyImage, String emptyText});

  ///带参数的对话框
  void startSubmit({String message});

  ///隐藏对话框
  void showSubmitSuccess();

  ///显示提交失败
  void showSubmitFailure(String code, String message);

  ///显示提示
  void showTips(String message);
}


import 'package:flutter/material.dart';
import 'package:flutter_mvp/mvp/presenter/i_present.dart';
import 'package:flutter_mvp/mvp/view/i_view.dart';

/// @desc  基础 widget,关联 Presenter,且与生命周期关联
/// @time 2019-04-22 11:08 am
/// @author Cheney
abstract class AbstractView extends StatefulWidget {}

abstract class AbstractViewState<P extends IPresenter, V extends AbstractView>
    extends State<V> implements IView {
  P presenter;

  @override
  void initState() {
    super.initState();
    presenter = createPresenter();
    if (presenter != null) {
      presenter.attachView(this);
    }
  }

  P createPresenter();

  P getPresenter() {
    return presenter;
  }

  @override
  void dispose() {
    super.dispose();
    if (presenter != null) {
      presenter.detachView();
      presenter = null;
    }
  }
}

复制代码

The IView interface defines methods for some public operations (loading state, no data state, error state, submission state, unified prompt, etc.). Here, you can define these public methods according to actual needs. I here is the base class by default In the process, you can refer to the Flutter base class BaseWidget package .

AbstractView abstract class inherits StatefulWidget, AbstractViewState defines a generic P inherits IPresenter, a generic V inherits AbstractView, implements IView, this abstract class holds a Presenter reference, and includes two life cycle methods initState and dispose for creating, Destroy the Presenter and call the attachView and detachView methods of the Presenter to associate the View and Model, and provide an abstract createPresenter for subclasses to implement.

Usage example

Here we take the login function module as an example:

 

 

 

Contract class

import 'package:flutter_mvp/model/i_model.dart';
import 'package:flutter_mvp/presenter/i_presenter.dart';
import 'package:flutter_mvp/view/i_view.dart';
import 'package:kappa_app/base/api.dart';

import 'login_bean.dart';

/// @desc 登录
/// @time 2020/3/18 4:56 PM
/// @author Cheney
abstract class View implements IView {
  ///登录成功
  void loginSuccess(LoginBean loginBean);
}

abstract class Presenter implements IPresenter {
  ///登录
  void login(String phoneNo, String password);
}

abstract class Model implements IModel {
  ///登录
  void login(
      String phoneNo,
      String password,
      SuccessCallback<LoginBean> successCallback,
      FailureCallback failureCallback);
}

复制代码

This defines the view interface, model interface and presenter interface of the login page.

In view, only define methods related to UI display, such as successful login.

The model is responsible for data requests, so only the login method is defined in the interface.

The presenter also only defines the login method.

Model class

import 'package:flutter_common_utils/http/http_error.dart';
import 'package:flutter_common_utils/http/http_manager.dart';
import 'package:flutter_mvp/model/abstract_model.dart';
import 'package:kappa_app/base/api.dart';

import 'login_bean.dart';
import 'login_contract.dart';

/// @desc 登录
/// @time 2020/3/18 4:56 PM
/// @author Cheney
class LoginModel extends AbstractModel implements Model {
  @override
  void dispose() {
    HttpManager().cancel(tag);
  }

  @override
  void login(
      String phoneNo,
      String password,
      SuccessCallback<LoginBean> successCallback,
      FailureCallback failureCallback) {
    HttpManager().post(
      url: Api.login,
      data: {'phoneNo': phoneNo, 'password': password},
      successCallback: (data) {
        successCallback(LoginBean.fromJson(data));
      },
      errorCallback: (HttpError error) {
        failureCallback(error);
      },
      tag: tag,
    );
  }
}

复制代码

Create a Model implementation class here, override the login method to give the return result of the login interface to the callback, override the dispose method to cancel the network request.

Presenter class

import 'package:flutter_common_utils/http/http_error.dart';
import 'package:flutter_mvp/presenter/abstract_presenter.dart';

import 'login_bean.dart';
import 'login_contract.dart';
import 'login_model.dart';

/// @desc 登录
/// @time 2020/3/18 4:56 PM
/// @author Cheney
class LoginPresenter extends AbstractPresenter<View, Model>
    implements Presenter {
  @override
  Model createModel() {
    return LoginModel();
  }

  @override
  void login(String phoneNo, String password) {
    view?.startSubmit(message: '正在登录');
    model.login(phoneNo, password, (LoginBean loginBean) {
      //取消提交框
      view?.showSubmitSuccess();
      //登录成功
      view?.loginSuccess(loginBean);
    }, (HttpError error) {
      //取消提交框、显示错误提示
      view?.showSubmitFailure(error.code, error.message);
    });
  }
}

复制代码

LoginPresenter inherits AbstractPresenter, passing in the View and Model generics

The createModel method is implemented to create the LoginMoel object, the login method is implemented, the login method in the model is called, and the data is obtained in the callback. You can also make some logical judgments and hand the results to the corresponding method of the view.

Note that view? is used here to solve the pointer problem when the view is empty.

Widget class

import 'package:flutter/material.dart';
import 'package:flutter_common_utils/lcfarm_size.dart';
import 'package:kappa_app/base/base_widget.dart';
import 'package:kappa_app/base/navigator_manager.dart';
import 'package:kappa_app/base/router.dart';
import 'package:kappa_app/base/umeng_const.dart';
import 'package:kappa_app/utils/encrypt_util.dart';
import 'package:kappa_app/utils/lcfarm_color.dart';
import 'package:kappa_app/utils/lcfarm_style.dart';
import 'package:kappa_app/utils/string_util.dart';
import 'package:kappa_app/widgets/lcfarm_input.dart';
import 'package:kappa_app/widgets/lcfarm_large_button.dart';
import 'package:kappa_app/widgets/lcfarm_simple_input.dart';
import 'package:provider/provider.dart';

import 'login_bean.dart';
import 'login_contract.dart';
import 'login_notifier.dart';
import 'login_presenter.dart';

/// @desc 登录
/// @time 2020/3/18 4:56 PM
/// @author Cheney
class Login extends BaseWidget {
  ///路由
  static const String router = "login";

  Login({Object arguments}) : super(arguments: arguments, routerName: router);

  @override
  BaseWidgetState getState() {
    return _LoginState();
  }
}

class _LoginState extends BaseWidgetState<Presenter, Login> implements View {
  LoginNotifier _loginNotifier;
  GlobalKey<FormState> _formKey = GlobalKey<FormState>();

  String _phoneNo = '';
  String _password = '';
  bool _submiting = false;

  bool isChange = false;

  @override
  void initState() {
    super.initState();
    setTitle('');
    _loginNotifier = LoginNotifier();
    isChange = StringUtil.isBoolTrue(widget.arguments);
  }

  @override
  void dispose() {
    super.dispose();
    _loginNotifier.dispose();
  }

  @override
  Widget buildWidget(BuildContext context) {
    return ChangeNotifierProvider<LoginNotifier>.value(
      value: _loginNotifier,
      child: Container(
        color: LcfarmColor.colorFFFFFF,
        child: ListView(
          children: [
            Padding(
              padding: EdgeInsets.only(
                top: LcfarmSize.dp(24.0),
                left: LcfarmSize.dp(32.0),
              ),
              child: Text(
                '密码登录',
                style: LcfarmStyle.style80000000_32
                    .copyWith(fontWeight: FontWeight.w700),
              ),
            ),
            _formSection(),
            Padding(
              padding: EdgeInsets.only(top: LcfarmSize.dp(8.0)),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  GestureDetector(
                    child: Padding(
                      padding: EdgeInsets.all(LcfarmSize.dp(8.0)),
                      child: Text(
                        '忘记密码',
                        style: LcfarmStyle.style3776E9_14,
                      ),
                    ),
                    behavior: HitTestBehavior.opaque,
                    onTap: () {
                      UmengConst.event(eventId: UmengConst.MMDL_WJMM);
                      NavigatorManager()
                          .pushNamed(context, Router.forgetPassword);
                    }, //点击
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  //表单
  Widget _formSection() {
    return Padding(
      padding: EdgeInsets.only(
          left: LcfarmSize.dp(32.0),
          top: LcfarmSize.dp(20.0),
          right: LcfarmSize.dp(32.0)),
      child: Form(
        key: _formKey,
        child: Column(
          children: <Widget>[
            LcfarmSimpleInput(
              hint: '',
              label: '手机号码',
              callback: (val) {
                _phoneNo = val;
                _buttonState();
              },
              keyboardType: TextInputType.phone,
              maxLength: 11,
              /*validator: (val) {
                return val.length < 11 ? '手机号码长度错误' : null;
              },*/
            ),
            LcfarmInput(
              hint: '',
              label: '登录密码',
              callback: (val) {
                _password = val;
                _buttonState();
              },
            ),
            Consumer<LoginNotifier>(
                builder: (context, LoginNotifier loginNotifier, _) {
              return Padding(
                padding: EdgeInsets.only(top: LcfarmSize.dp(48.0)),
                child: LcfarmLargeButton(
                  label: '登录',
                  onPressed:
                      loginNotifier.isButtonDisabled ? null : _forSubmitted,
                ),
              );
            }),
          ],
        ),
      ),
    );
  }

  //输入校验
  bool _fieldsValidate() {
    //bool hasError = false;
    if (_phoneNo.length < 11) {
      return true;
    }
    if (_password.isEmpty) {
      return true;
    }
    return false;
  }

  //按钮状态更新
  void _buttonState() {
    bool hasError = _fieldsValidate();
    //状态有变化
    if (_loginNotifier.isButtonDisabled != hasError) {
      _loginNotifier.isButtonDisabled = hasError;
    }
  }

  void _forSubmitted() {
    var _form = _formKey.currentState;
    if (_form.validate()) {
      //_form.save();
      if (!_submiting) {
        _submiting = true;
        UmengConst.event(eventId: UmengConst.MMDL_DL);
        EncryptUtil.encode(_password).then((pwd) {
          getPresenter().login(_phoneNo, pwd);
        }).catchError((e) {
          print(e);
        }).whenComplete(() {
          _submiting = false;
        });
      }
    }
  }

  @override
  void queryData() {
    disabledLoading();
  }

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

   @override
  void loginSuccess(LoginBean loginBean) async {
    await SpUtil().putString(Const.token, loginBean.token);
    await SpUtil().putString(Const.username, _phoneNo);
    NavigatorManager().pop(context);
  }
  
}

复制代码

Login here is the view of the login function module, inheriting from BaseWidget, and passing in the view and presenter generics. Implement the LoginContract.View interface and rewrite the UI methods defined by the interface.

Create a LoginPresenter object in the createPresenter method and return. So you can use getPresenter to directly manipulate the logic.

Code plugin

Using MVP will add some additional interfaces and classes, and their format is relatively uniform. In order to unify the standard code, the code of related MVP is generated uniformly using AS plug-ins.

Integrate plugins in the IDE

Download the plugin under the plugin, open the IDE preferences, find plugins, select install plugin from disk, find the plugin we just downloaded, restart the IDE to take effect.

 

 

 

Generate code

Quickly Generate... in the newly created contract class. Find FlutterMvpGenerator, and the model, presenter, and widget classes of the corresponding module will be generated.

 

 

 

At last

Using the MVP mode will make the application easier to maintain, and it will also facilitate our testing.

Pub library address

Plug-in address

Learning materials

Guess you like

Origin blog.csdn.net/u013491829/article/details/109355402
MVP