The MVP mode of the Android framework (take login as an example)

    The Android framework now commonly has MVC mode, MVP mode, and MVVM mode. Let us first clarify a concept: a pattern refers to a structural way of organizing code, and a pattern cannot improve the efficiency of code execution. The mode is used for the convenience of subsequent function expansion and the clear structure of the code.

    In the previous article, from the beginning, the code was written in the Activity to evolve the Android MVC model, but in the end, we may still feel that the code in the Activity is a bit complicated, this is because of the roles of the V layer and the C layer All are borne by the Activity, if we want to go further, we need to further understand the MVP mode. 

The MVP mode divides the software into three basic parts: Model, View and Presenter (I will translate it into Presenter for the time being).

    Model-used to generate data (access to the network, database, etc.)

    View (View)-view layer related (interface layout controls and other related codes: Activity, Fragment)

    Presenter-connecting M and V (code related to business logic)

In the MVC mode, Activity plays the role of both the V layer and the C layer, that is, in addition to the related code such as layout controls and business logic related code in the Activity, we extract all the business logic related code in the Activity Leaving becomes an MVP mode. In the MVP mode, Activity only contains code related to interface layout controls, and no longer contains code related to business logic. Activity only assumes the role of the View layer. In addition, the Model layer and the View layer do not directly interact, but the Presenter acts as an intermediary, and the Presenter will hold the Model and View objects.

1. Extract the relevant code of business logic and realize the MVP mode

First create a presenter package in the project, then create the LoginPresenter.java class, extract the business logic-related code in the Activity to form the presenter layer of login:

//登录模块的业务逻辑
public class LoginPresenter {
    private static final String TAG = "LoginPresenter";

    private LoginActivity loginActivity;

    public LoginPresenter(LoginActivity loginActivity) {
        this.loginActivity = loginActivity;
    }

    public void login(String phoneNumber, String password) {
        //本地对输入情况做校验
        boolean validateOk = validateInput(phoneNumber, password);
        if (validateOk) {
            loginActivity.showProgressBar();
            String md5Password = MD5Utils.getMd5(password);

            LoginModel loginModel = new LoginModel();
            loginModel.gotoLogin(phoneNumber, md5Password, new LoginModel.OnNetResponseListener() {
                @Override
                public void onNetResposeError(String msg) {
                    loginActivity.hideProgressBar();
                    loginActivity.showToast(msg);
                }

                @Override
                public void onNetReponseSuccess(LoginData loginData) {
                    loginActivity.hideProgressBar();
                    switch (loginData.status) {
                        case 200: //用户名未注册
                        case 201: //密码有误
                        case 203: //登录失败
                            loginActivity.showToast(loginData.message);
                            Log.i(TAG, "onResponse: = " + loginData.message);
                            break;
                        case 202:   //登录成功
                            loginActivity.showToast(loginData.message);
                            Log.i(TAG, "onResponse: = " + loginData.message);

                            //本地保存必要的用户信息
                            //......
                            loginActivity.jumpSuccessActivity(loginData);
                            break;
                        default:
                            loginActivity.showToast("登录出现未知异常");
                            break;
                    }
                }
            });
        }
    }

    private boolean validateInput(String phoneNumber, String password) {
        if (TextUtils.isEmpty(phoneNumber)) {
            loginActivity.showToast("手机号不能为空");
            return false;
        }
        if (TextUtils.isEmpty(password)) {
            loginActivity.showToast("密码不能为空");
            return false;
        }
        if (!phoneNumber.matches(Constants.STR_PHONE_REGEX2)) {  //匹配正则表达式
            loginActivity.showToast("请输入正确的手机号");
            return false;
        }
        return true;
    }
}

At this point, Activity only has relevant code such as interface layout controls, that is, the View layer becomes:

public class LoginActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = "LoginActivity";

    private EditText    etPhoneNumber;
    private EditText    etPassword;
    private Button      btnLogin;
    private ProgressBar progressBar;

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

        etPhoneNumber = (EditText) findViewById(R.id.et_phone_number);
        etPassword = (EditText) findViewById(R.id.et_password);
        btnLogin = (Button) findViewById(R.id.btn_login);
        progressBar = (ProgressBar) findViewById(R.id.progressBar);

        btnLogin.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_login:    //登录
                String phoneNumber = etPhoneNumber.getText().toString().trim();
                String password = etPassword.getText().toString().trim();
                //login(phoneNumber, password);
                LoginPresenter loginPresenter = new LoginPresenter(this);
                loginPresenter.login(phoneNumber, password);
                break;
            default:
                break;
        }
    }

    public void showProgressBar(){
        progressBar.setVisibility(View.VISIBLE);
    }

    public void hideProgressBar(){
        progressBar.setVisibility(View.GONE);
    }
    
    public void showToast(String toast){
        Toast.makeText(this, toast, Toast.LENGTH_SHORT).show();
    }

    public void jumpSuccessActivity(LoginData loginData){
        Intent intent = new Intent(LoginActivity.this, LoginSuccessActivity.class);
        startActivity(intent);
        //登录页面直接消失
        finish();
    }
}

The Model layer is still the same as the original one, unchanged:

//Model层只用来产生数据的
public class LoginModel {
    private static final String TAG = "LoginModel";

    public void gotoLogin(String phoneNumber, String md5Password, final OnNetResponseListener listener) {
        OkHttpUtils
                .post()
                .url(Constants.URL_LOGIN)
                .addParams("phoneNumber", phoneNumber)
                .addParams("password", md5Password)
                .build()
                .execute(new StringCallback() {
                    @Override
                    public void onError(okhttp3.Call call, Exception e, int id) {
                        Log.i(TAG, "onError: ---网络访问出现异常---" + e.getMessage());
                        e.printStackTrace();
                        listener.onNetResposeError("网络访问出现异常");
                    }

                    @Override
                    public void onResponse(String response, int id) {
                        Log.i(TAG, "onResponse: 登录成功 response = " + response + " ---");
                        Gson gson = new Gson();
                        LoginData loginData = gson.fromJson(response, LoginData.class);
                        listener.onNetReponseSuccess(loginData);
                    }
                });
    }

    public interface OnNetResponseListener {

        void onNetResposeError(String msg);

        void onNetReponseSuccess(LoginData loginData);
    }
}

At this time, LoginActivity has been separated into three independent M, V, and P layers: the Model layer is only used to generate data, the View layer, that is, the Activity only contains relevant code such as interface layout controls, and the Presenter layer is responsible for the processing of related business logic. And the Model layer and the View layer do not interact directly.

Second, the extension of the applicability of the Presenter layer

    In the above example, we use LoginActivity as the View layer. If the login page becomes a Fragment such as LoginFragment, not only the code of the View layer needs to be changed, but the code of the Presenter layer also needs to be modified, because the View layer object held by the LoginPresenter above is LoginActivity, so obviously It is inappropriate, because the Presenter layer is responsible for the processing of business logic and does not care about whether the View layer is Activity or Fragment. Changes in the View layer should not affect the code of the Presenter layer. The code of the Presenter layer should be relatively independent.

At this time, we need to define a  rule (specification) for the View layer, that is, an interface. The View layer object supported by the Presenter layer is an interface, and the Presenter layer does not need to pay attention to the specific implementation of this interface. We create a new view package and create an ILoginView interface to standardize UI operations related to the login page:

public interface ILoginView {

    public void showProgressBar();

    public void hideProgressBar();

    public void showToast(String toast);

    public void jumpSuccessActivity(LoginData loginData);
}

Then change the View layer reference held by LoginPresenter above to the ILoginView interface:

//登录模块的业务逻辑
public class LoginPresenter {
    private static final String TAG = "LoginPresenter";

    private ILoginView loginView;

    public LoginPresenter(ILoginView loginView) {
        this.loginView = loginView;
    }

    public void login(String phoneNumber, String password) {
        //本地对输入情况做校验
        boolean validateOk = validateInput(phoneNumber, password);
        if (validateOk) {
            loginView.showProgressBar();
            String md5Password = MD5Utils.getMd5(password);

            LoginModel loginModel = new LoginModel();
            loginModel.gotoLogin(phoneNumber, md5Password, new LoginModel.OnNetResponseListener() {
                @Override
                public void onNetResposeError(String msg) {
                    loginView.hideProgressBar();
                    loginView.showToast(msg);
                }

                @Override
                public void onNetReponseSuccess(LoginData loginData) {
                    loginView.hideProgressBar();
                    switch (loginData.status) {
                        case 200: //用户名未注册
                        case 201: //密码有误
                        case 203: //登录失败
                            loginView.showToast(loginData.message);
                            Log.i(TAG, "onResponse: = " + loginData.message);
                            break;
                        case 202:   //登录成功
                            loginView.showToast(loginData.message);
                            Log.i(TAG, "onResponse: = " + loginData.message);

                            //本地保存必要的用户信息
                            //......
                            loginView.jumpSuccessActivity(loginData);
                            break;
                        default:
                            loginView.showToast("登录出现未知异常");
                            break;
                    }
                }
            });
        }
    }

    private boolean validateInput(String phoneNumber, String password) {
        if (TextUtils.isEmpty(phoneNumber)) {
            loginView.showToast("手机号不能为空");
            return false;
        }
        if (TextUtils.isEmpty(password)) {
            loginView.showToast("密码不能为空");
            return false;
        }
        if (!phoneNumber.matches(Constants.STR_PHONE_REGEX2)) {  //匹配正则表达式
            loginView.showToast("请输入正确的手机号");
            return false;
        }
        return true;
    }
}

At this time, LoginPresenter does not need to care about whether the specific implementation of the View layer is Activity or Fragment, it only needs them to implement the ILoginView interface and rewrite the abstract method. This greatly increases the applicability of the presenter layer. Even according to this idea, we can create an interface for Presenter and let LoginPresenter implement this interface.

The final package structure should look like this:

The above is the MVP mode realized by logging in as an example.

Guess you like

Origin blog.csdn.net/beita08/article/details/82725896