This article briefly describes the problems encountered in the use of MVP architecture in the actual project and the corresponding solutions, and finally sorted out the upgraded VERSION of MVP architecture.

Android Architecture series

This series of articles will update you on some of the best architecture and tips for Android project development

How to write an Intent series 3 Android architecture series 3 Android architecture series 4 Android architecture series 5 Encapsulate your own OkHTTP series Android Architecture Series – Practical application of MVP architecture

1 Original MVP structure

Using the MVP architecture was introduced in the first article in this series. Check back for details

The MVP structure looks like this:

MVP

2. Problems in the application of actual projects

MVP is a layering idea of code that doesn’t actually use any libraries, but just tells you how to place your code neatly. Make each level of code do its job, increasing readability and testability.

In real development, however, MVP is a highly cohesive pattern in a module where the Presenter layer takes over the logical implementation of the Activity. The following problems have arisen:

2.1 Presenter lifecycle issues

The Presnter layer and the View layer are one-to-one, so the Presnter layer and the View layer have the same life cycle.

But now all the logic is written in the Presenter layer, and if it needs to be called elsewhere it can only be called through static methods. You can’t create a Presenter instance again

2.2 Cross-module invocation

In the actual development, there is often part of the logic in B module to call A module.

For example, when Posting, we should judge whether the user is logged in or not, and obtain the information of the current logged in user. That is, in the post module to get the user module’s data and logic.

If the logic is written in Presenter, other modules can only read the current user cache directly and then parse it in their own modules. There is also increased coupling between modules.

3. Optimized MVP layering

Name the Model layer Interactor here. We write the atomic logic inside each module (a function rather than a series of logical functions) in interactor. The Presenter layer is only responsible for receiving view events, calling interactor functions, and giving back to the View.

Here, a Presenter can hold an Interactor of multiple modules so that they can access functional logic and data. And there is no need to analyze the data of other modules in their own modules.

new MVP

The main difference between this optimized layer and the regular MVP is that it frees up the Presenter layer, which no longer holds specific logic, but calls logic directly.

Analyze each layer:

3.1 the View layer

  1. It only holds its own one-to-one instance of Presenter, which is called by implementing the interface
  2. Controls that initialize the page, refresh the display page, and listen for element events
  3. There should be no status, logic, etc. (unless there is very little logic related to the page, such as a field indicating whether the password is visible)

3.2 the Presenter layer

  1. Hold and own one – to – one corresponding View instance, can hold multiple modules of the Interactor layer. Call by implementing the interface.
  2. The Glue layer, which is the View and Interactor layer, receives View operations, calls methods in the module, and returns data to the View.

3.3 Interactor layer

  1. The atomic logic in this module is encapsulated, not as a family of logic, so that it can be easily called elsewhere.
  2. References to other modules should not appear in the Interactor layer
  3. Return of the Interactor layer. Callback is defined in contract.interactor if it is synchronous and if asynchronous.

4 A code example

Here is a brief description of the Sample login code

LoginContract layer:

public interface LoginContract {

    interface View extends BaseView {
        /** * jump Home */
        void goHome(a);
    }

    interface Presenter extends BasePresenter {
        /**
         * login
         * @param phone
         * @param password
         */
        void onLogin(String phone, String password);
    }

    interface Interactor {
        /**
         * do login
         * @param phone
         * @param password
         * @param callback
         */
        void doLogin(String phone, String password, LoginCallback callback);
        interface LoginCallback {
            void onSuccess(UserInfo user_info);
            void onFailure(String msg);
        }

        /** * Whether to log in *@return* /
        boolean isLogin(a);

        /** * Get the current login user *@return* /
        UserInfo getLoginUser(a); }}Copy the code

LoginActivity:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        ButterKnife.bind(this);

        mPresenter = new LoginPresenter(this);
    }

    @OnClick(R.id.btnLogin)
    public void onLogin(a) {
        mPresenter.onLogin(editPhone.getText().toString(), editPassword.getText().toString());
    }

    @OnClick(R.id.txtRegister)
    public void goRegister(a) {
        ToastUtils.showShort(GlobalApp.getInstance().getContext(), "goRegister");
    }

    @OnClick(R.id.txtForgetPwd)
    public void goForgetPwd(a) {
        ToastUtils.showShort(GlobalApp.getInstance().getContext(), "goForgetPwd");
    }

    @Override
    public void showToast(String msg) {
        ToastUtils.showShort(GlobalApp.getInstance().getContext(), msg);
    }

    @Override
    public void goHome(a) {
        // Go to the Home page
        ToastUtils.showShort(GlobalApp.getInstance().getContext(), "Login successful, go to Home page");

        Intent intent = new Intent(this, HomeActivity.class);
        startActivity(intent);
        finish();
    }Copy the code

LoginPresnter:

public class LoginPresenter implements LoginContract.Presenter {

    private LoginContract.View mView;
    private LoginContract.Interactor mInteractor;

    public LoginPresenter(LoginContract.View view) {
        mView = view;
        mInteractor = new LoginInteractor();
    }

    @Override
    public void start(a) {}@Override
    public void onLogin(String phone, String password) {
        if(StringUtils.isEmpty(phone)) {
            mView.showToast("Empty phone");
            return;
        }

        if(StringUtils.isEmpty(password)) {
            mView.showToast("Empty password");
            return;
        }

        mInteractor.doLogin(phone, password, new LoginContract.Interactor.LoginCallback() {
            @Override
            public void onSuccess(UserInfo user_info) {
                mView.goHome();
            }

            @Override
            public void onFailure(String msg) { mView.showToast(msg); }}); }}Copy the code

LoginInteractor:

public class LoginInteractor implements LoginContract.Interactor {

    private MyOkHttp mApi;
    private ACache mCache;

    / / the cache key
    private final String CACHE_KEY_USERINFO = "CACHE_KEY_USERINFO";

    public LoginInteractor(a) {
        mApi = MyOkHttp.get();
        mCache = ACache.get(GlobalApp.getInstance().getContext());
    }

    @Override
    public void doLogin(String phone, String password, final LoginCallback callback) {
        // Simulate an asynchronous network request to log in

        Handler handler = new Handler();

        handler.postDelayed(new Runnable() {
            @Override
            public void run(a) {
                UserInfo userInfo = new UserInfo();
                userInfo.uid = "1212121";
                userInfo.userName = "tsy12321";
                userInfo.token = "wqw13w12312wsqw12";

                // CachemCache.put(CACHE_KEY_USERINFO, userInfo); callback.onSuccess(userInfo); }},2000);
    }

    @Override
    public boolean isLogin(a) {
        UserInfo userInfo = (UserInfo) mCache.getAsObject(CACHE_KEY_USERINFO);
        if(! StringUtils.isEmpty(userInfo.uid) && ! StringUtils.isEmpty(userInfo.token)) {return true;
        }
        return false;
    }

    @Override
    public UserInfo getLoginUser(a) {
        return(UserInfo) mCache.getAsObject(CACHE_KEY_USERINFO); }}Copy the code

As you can see from the examples above, the specific logic is placed in the Interactor layer. The following shows how other modules call the logic or data of the Login module.

If you click post on the Home page, you need to determine the current login status. It also holds an instance of LoginInteractor in HomePresenter.

public class HomePresenter implements HomeContract.Presenter {

    private HomeContract.View mView;
    private HomeContract.Interactor mInteractor;
    private LoginContract.Interactor mLoginInteractor;

    public HomePresenter(HomeContract.View view) {
        mView = view;
        mInteractor = new HomeInteractor();
        mLoginInteractor = new LoginInteractor();
    }

    @Override
    public void start(a) {}@Override
    public void onPost(a) {
        // Check whether the user is logged in
        if(! mLoginInteractor.isLogin()) {// Jump to login
            // TODO: 16/8/30
            return;
        }

        // Jump to the post page
        // TODO: 16/8/30}}Copy the code

Specific code in Github project: BaseAndroidProject

At the end of may

Whether it’s an MVP or an architecture, the ultimate goal is to write readable and testable code. So don’t over-design your architecture. The architecture will naturally evolve during actual development. Keep in mind! Balanced and reasonable!!

More articles follow my public account

My official account