This article is mainly based on their own understanding of MVP to build the MVP framework in line with their own business scenarios.

Put a Demo address first, also at the end of the article

About the MVP

  • M (Model) is responsible for data request, analysis, filtering and other data operations.
  • V (View) handles the UI, usually withActivity FragmentAppears in the form of.
  • P (Presenter) View Model middleware, interactive bridge.

    The figure above is quoted from

The benefits of the MVP

  • Decoupled UI logic from business logic, reducing coupling.
  • The Activity only handles UI-related operations, making the code more concise.
  • UI logic and business logic are abstracted into interfaces for easy reading and maintenance.
  • Pull business logic into Presenter to avoid memory leaks caused by complex business logic.

The specific implementation

1. View encapsulation In general, do data requests have display loading box, request success, request failure and other operations, we encapsulated these common functions into BaseView.

Public interface IBaseView {/** * display loading box */ void showLoading(); /** * dismissLoading(); /** * empty data ** @param tag */ void onEmpty(Object tag); /** * error data ** @param tag tag * @param errorMsg Error message */ void onError(Object tag, String errorMsg); /** * context ** @return context
     */
    Context getContext();
}
Copy the code

To avoid memory leaks caused by time-consuming operations performed by presenters holding views, our presenters should be created and destroyed at the same time as the host Activity/Fragment.

public abstract class BasePresenter{ ... Public void attachView(View) {this. View = View; } /** * unbind View */ public voiddetachView() { this.view=null; }... } public abstract class MvpActivity<P extends BasePresenter> extends BaseActivity implements IBaseView{ ... @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Present Presenter = createPresenter();if(presenter ! = null) { presenter.attachView(this); } } @Override protected voidonDestroy() {
        super.onDestroy();
        if(presenter ! = null) { presenter.detachView(); presenter = null; }}... }Copy the code

The above actions can solve the memory leak problem, but they can cause problems in the line:

Scenario: When a user opens the commodity list page, the network is poor and data acquisition is slow. The user leaves the page and continues to browse other pages, and the application crashes suddenly.

Analysis problem: P and V are bound when the user opens the page, and P and V are unbound when the user leaves the page. When the time-consuming operation is completed and the update interface of V is called, since P and V have been unbound and V is null, calling the update page method of V will cause null pointer exception.

Fix the problem: Use a dynamic proxy to weak reference a View. The complete BasePresenter is as follows:

public abstract class BasePresenter<M extends IBaseModel, V extends IBaseView> { private V mProxyView; private M module; private WeakReference<V> weakReference; /** * bind View */ @suppressWarnings ("unchecked")
    public void attachView(V view) {
        weakReference = new WeakReference<>(view);
        mProxyView = (V) Proxy.newProxyInstance(
                view.getClass().getClassLoader(),
                view.getClass().getInterfaces(),
                new MvpViewHandler(weakReference.get()));
        if(this.module == null) { this.module = createModule(); } /** * unbind View */ public voiddetachView() {
        this.module = null;
        if(isViewAttached()) { weakReference.clear(); weakReference = null; }} /** * Whether to connect to View */ protected BooleanisViewAttached() {
        returnweakReference ! = null && weakReference.get() ! = null; } protected VgetView() {
        return mProxyView;
    }

    protected M getModule() {
        return module;
    }

    protected Context getContext() {
        return getView().getContext();
    }

    protected void showLoading() {
        getView().showLoading();
    }

    protected void dismissLoading() { getView().dismissLoading(); } /** * createModule */ protected createModule(); Public void start(); public void start(); /** * private class MvpViewHandler implements InvocationHandler {private IBaseView implements InvocationHandler mvpView; MvpViewHandler(IBaseView mvpView) { this.mvpView = mvpView; } @override public Object invoke(Object proxy, Method Method, Object[] args) throws Throwable {// If layer V is not destroyed, execute the layer V Method.if (isViewAttached()) {
                returnmethod.invoke(mvpView, args); } // The P layer does not care about the return value of the V layerreturnnull; }}}Copy the code

Contract classes manage all interfaces of Model, View and Presenter through Contract classes. In this way, the functions of Presenter and View are clear and easy to maintain. At the same time, View and Presenter correspond one by one. And effectively reduce the number of classes.

public interface Contract { interface Model extends IBaseModel { void login(User user, ResponseCallback callback); } interface View extends IBaseView { User getUserInfo(); void loginSuccess(User user); } interface Presenter { void login(); }}Copy the code

4. The same is true for Fragment encapsulation of activities

public abstract class BaseMvpActivity<P extends BasePresenter> extends Activity implements IBaseView {

    protected P presenter;

    @SuppressWarnings("unchecked") @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Present Presenter = createPresenter();if(presenter ! = null) { presenter.attachView(this); } } @Override protected voidonDestroy() {
        super.onDestroy();
        if(presenter ! = null) { presenter.detachView(); presenter = null; } } @Override public voidshowLoading() {
        if(loadingDialog ! = null && ! loadingDialog.isShowing()) { loadingDialog.show(); } } @Override public voiddismissLoading() {
        if(loadingDialog ! = null && loadingDialog.isShowing()) { loadingDialog.dismiss(); } } @Override public void onEmpty(Object tag) { } @Override public void onError(Object tag, String errorMsg) { } @Override public ContextgetContext() {
        returnmContext; } /** * createPresenter */ protected createPresenter(); }Copy the code

By specifying Presenter by generics and exposing the abstract method createPresenter() to subclasses to createPresenter, the base class implements the common methods in BaseView, reducing the redundancy of subclass code. 5. Login case

Public interface LoginContract {interface Model extends IBaseModel {/** * login ** @param user user information * @param callback  */ void login(User user, ResponseCallback callback); } interface View extends IBaseView {/** * returns User information */ User getUserInfo(); /** * loginSuccess */ void loginSuccess(User User); } interface Presenter {/** * login */ void login(); }}Copy the code

Model

public class LoginModel implements LoginContract.Model {

    @Override
    public void login(User user, ResponseCallback callback) {
        if (user == null) {
            callback.onError("", (Throwable) new Exception("User information is empty"));
        }
        RequestParam param = new RequestParam();
        param.addParameter("username", user.getUsername());
        param.addParameter("password", user.getPassword()); HttpUtils.getInstance() .postRequest(Api.LOGIN, param, callback); }}Copy the code

Presenter

public class LoginPresenter extends BasePresenter<LoginContract.Model, LoginContract.View>
        implements LoginContract.Presenter {

    @Override
    public void login() {
        if (isViewAttached()) {
            getView().showLoading();
            getModule().login(getView().getUserInfo(), new OnResultObjectCallBack<User>() {
                @Override
                public void onSuccess(boolean success, int code, String msg, Object tag, User response) {
                    if(code == 0 && response ! = null) { getView().loginSuccess(response); }else {
                        getView().onError(tag, msg);
                    }
                }

                @Override
                public void onFailure(Object tag, Exception e) {
                    getView().onError(tag, msg);
                }

                @Override
                public void onCompleted() { getView().dismissLoading(); }}); } } @Override protected LoginModelcreateModule() {
        return new LoginModel();
    }

    @Override
    public void start() {}}Copy the code

Log on to the Activity

public class LoginActivity extends ActionBarActivity<LoginPresenter> implements LoginContract.View {

    @BindView(R2.id.edt_name)
    EditText edtName;

    @BindView(R2.id.edt_pwd)
    EditText edtPwd;

    @BindView(R2.id.ob_login)
    ObserverButton obLogin;

    @BindView(R2.id.ob_register)
    TextView obRegister;

    @Override
    protected int getLayoutId() {
        return R.layout.user_activity_login;
    }

    @Override
    protected void initView() {
        setTitleText("Login");
        obLogin.observer(edtName, edtPwd);
    }


    @OnClick({R2.id.ob_login, R2.id.ob_register})
    public void onViewClicked(View view) {
        int i = view.getId();
        if (i == R.id.ob_login) {
            presenter.login();
        } else if (i == R.id.ob_register) {
            ActivityToActivity.toActivity(mContext, RegisterActivity.class);
        }
    }

    @Override
    public void loginSuccess(User user) {
        UserInfoUtils.saveUser(user);
        EventBusUtils.sendEvent(new Event(EventAction.EVENT_LOGIN_SUCCESS));
        finish();
    }


    @Override
    public void onError(Object tag, String errorMsg) {
        super.onError(tag, errorMsg);
        ToastUtils.showToast(mContext, errorMsg);
    }

    @Override
    protected LoginPresenter createPresenter() {
        return new LoginPresenter();
    }

    @Override
    public void onEventBus(Event event) {
        super.onEventBus(event);
        if (TextUtils.equals(event.getAction(), EventAction.EVENT_REGISTER_SUCCESS)) {
            finish();
        }
    }

    @Override
    protected boolean regEvent() {
        return true;
    }

    @Override
    public User getUserInfo() {
        returnnew User(edtName.getText().toString().trim(), edtPwd.getText().toString().trim()); }}Copy the code

conclusion

Whether it’s MVP or MCV or MVVM, the goal is to separate the business from the UI and avoid cramming all operations into one Activity, each working in its own domain. Everyone has a different understanding and perspective on hierarchy, and it is important to encapsulate a framework that is right for you and the current business scenario.

The MVP structure is used in this framework

Finally put the Demo address, study together, what is not good, welcome to point out!

A brief introduction to the MVP architecture in Android in-depth explanation of the Android MVP framework