MVP mode of Android



MVP mode


Android MVP Pattern

Android MVP mode1 It's nothing new, and I've used this design pattern extensively in my own projects. When the project is more and more large and complex, and more and more R&D personnel are involved, the advantages of MVP model are fully demonstrated.

Guide: MVP mode is a variant of MVC mode on Android. To introduce MVP, you must first introduce MVC. In MVC mode, activity should belong to the View layer. In essence, it takes on both View and Controller. This is not very friendly for development and maintenance, and has a high degree of coupling. Extracting the View and Controller of Activity becomes View and Presenter, which is MVP mode.

Essential information

MVP (Model-View-Presenter) can be said to be a variant and evolutionary model of MVC (Model-View-Controller) in Android development. The latter may be familiar with, even if not familiar with, it may be more or less used in their own projects. To introduce MVP mode, we have to talk about MVC mode first.

MVC mode

The structure of MVC pattern is divided into three parts: Model of entity layer, View of view layer and Controller of control layer.

  • The View layer is actually the UI interface of the program, which is used to show the data to the user and receive the user's input.

  • The Model layer is the JavaBean entity class, which is used to save instance data.

  • Controller Controller Controller Used to Update UI Interfaces and Data Instances

For example, the View layer accepts user input, and then modifies the corresponding Model instance through Controller; at the same time, when the data of the Model instance changes, the UI interface needs to be modified, and the interface can be updated through Controller. (View layer can also update the data of the Model instance directly, instead of using Controller every time, which makes it much easier for some simple data updates.)

For example, to implement a dynamic wallpaper, Snow can be defined as an entity class, which stores XY axis coordinate data. The View layer is SurfaceView (or other views). In order to achieve the effect of snowflakes, you can start a background thread and update Snow reality continuously in the thread. The coordinate values in the example, which is part of Controller's work, also update the snow on Surface View regularly in Controller. Further, you can monitor the user's clicks on Surface View. If the user clicks, only the coordinates of Snow around the touch point are adjusted by Controller, so that the snowflake pops up after the user clicks. Specific MVC model please Google.

MVP mode

In the Android project, Activity and Fragment take up most of the development work. If there is a design pattern (or code structure) specifically designed to optimize the code for Activity and Fragment, do you think this pattern is important? This is the MVP design pattern.

According to the hierarchy of MVC, Activity and Fragment (Activity is only mentioned later) should belong to the View layer, which is used to display UI interface and receive user input, in addition to some life cycle work. Activity plays a very important role in Android development, especially TA's life cycle function, so when we develop, we often write some business logic directly in Activity, which is very intuitive and convenient. The cost is that Activity will become more and more bloated. More than 1000 lines of code is a common thing, and if so. Some common business logic (such as user login) can be written in a specific activity, which means that the logic can not be reused. If you have experience with code refactoring, you'll be worried about seeing a class with 1000 + lines. Therefore, Activity not only assumes the role of View, but also a part of Controller, so that V and C are coupled together. Although this is convenient to write, it is difficult to maintain if the business is adjusted, and the code to find business logic in a bloated Active class will be very painful. So it seems necessary to pull View and Controller out of Activity, and that's what MVP does.

The core idea of MVP mode is:

MVP abstracts UI logic in Activity into View interface, business logic into Presenter interface, and Model class is the original Model.

This is the MVP pattern. Now, in that case, Activity's work is simple, only to respond to the life cycle, and the rest of the work is lost to Presenter. As can be seen from the figure above, Presenter is a bridge between Model and View. In order to make the structure simpler, View can not operate on Model directly. This is also the biggest difference between MVP and MVC.

The Role of MVP Model

What are the advantages of MVP? If anyone is right, give him KIRA! (<)

  • Separation of view logic and business logic reduces coupling

  • Activity only deals with lifecycle tasks, and the code becomes more concise

  • View logic and business logic are abstracted into the interfaces of View and Presenter respectively to improve the readability of code.

  • Presenter is abstracted as an interface and can be implemented in many ways, so it is convenient for unit testing.

  • Draw business logic into Presenter to avoid background threads referring to Activity, which makes it impossible for Activity resources to be recycled by the system, thus causing memory leaks and OOM.

Three of the most important points are:

Activity code becomes more concise

I believe that many people start reading code from Activity, looking at a 1000 + line of code Activeness, feel uncomfortable.

With MVP, Activity can lose a lot of weight, basically just FindView, SetListener and Init code. The other is the call to Presenter and the implementation of the View interface. In this case, it's much easier to read code, and if you look at Presenter's interface, you can see what business this module has, and you can quickly locate the specific code. Activity becomes easy to understand and maintain, and it becomes much simpler to adjust business and delete functions in the future.

Convenient unit testing

General unit testing is used to test some new business logic is not a problem, if the traditional code style (habitually called MV mode, less P), we may have to write a test code in Activity, after testing, delete the test code and replace it with formal code, then if business has been found. The problem has to be changed back to the test code. Well, the test code has been deleted! Okay, rewrite it...

In MVP, because the business logic is in Presenter, we can write an implementation class of PresenterTest to inherit the interface of Presenter. Now, as long as the creation of PresenterTest is replaced by PresenterTest in Activity, unit testing can be carried out, and then it can be replaced. In case you find that you have to test, replace it with Presenter Test.

Avoid Activity Memory Leakage

The biggest reason for the OOM of Android APP is that memory leaks cause the memory shortage of APP. One of the two main reasons for memory leaks is Activity Leak (another is Bitmap Leak).

A powerful function of Java is the memory recovery mechanism of its virtual machine, which enables Java users to design code without considering object recovery as C++ users do. However, Java users always like to write a bunch of objects casually, and then fantasize that the virtual machine can help them deal with memory recycling. However, when the virtual machine reclaims memory, it only reclaims objects that are not referenced. The referenced objects can not be reclaimed because they may also be called.

Activity has a life cycle. Users can switch Activity at any time. When the memory of APP is not enough, the system will recycle the resources of Activity in the background to avoid OOM.

With the traditional MV mode, a lot of asynchronous tasks and UI operations are placed in Activity. For example, you may download a picture from the network and load the picture into the Activity's ImageView in the successful callback of download, so the asynchronous tasks retain the reference to Activity. In this way, even if Activity has been switched to the background (onDestroy has been executed), these asynchronous tasks still retain references to the Activity instance, so the system can not recycle the Activity instance, resulting in Activity Leak. In Android components, Activity objects often occupy the most memory in the heap (Java Heap), so the system will recycle Activity objects first. If there is Activity Leak, APP will easily be OOM because of insufficient memory.

Using MVP mode, Activity Leak can be avoided as long as the reference of asynchronous tasks to Activity is separated in the current Activity's on Destroy.

Said so much, did not understand? Well, I don't understand what I wrote myself. Let's just read the code.

The Use of MVP Mode

As can be seen from the UML diagram of the above simple MVP pattern, using MVP requires at least the following steps:

  1. Create the IPresenter interface, put all the business logic interfaces here, and create its implementation PresenterCompl (here you can easily view business functions, because there are many implementations of the interface, so it is also convenient to write unit tests)

  2. Create the IView interface and put all the interfaces of view logic here. Its implementation class is the current Activity/Fragment

  3. As can be seen from the UML diagram, Activity contains an IPresenter, while PresenterCompl contains an IView and relies on the Model. Only IPresenter calls are retained in Activity, and all other work is left to PresenterCompl for implementation.

  4. Model s are not necessary, but there must be View s and Presenter s

Through the introduction above, the main feature of MVP is to extract many logic from Activity into View and Presenter interfaces, and to be completed by specific implementation classes. This way of writing has a lot of interfaces between IView and IPresenter, which increases the workload of development to a certain extent. Small partners who start using MVP may find this way of writing awkward and difficult to remember. In fact, it is useless to think too much at first. If you write more than a few times in a specific project, you will be familiar with the writing of MVP mode, understand the intention of TA, and enjoy the benefits brought by it.

So much, but it doesn't seem to have any eggs, after all.

Talk is cheap, let me show you the code!

So let's write down the actual project.

Simple example of MVP pattern

A simple login interface (unexpectedly ()), click LOGIN to verify the account password, click CLEAR to reset the input.

The project structure looks like this, and the layering of MVP is clear. My habit is to divide the module into packages first, and then create the sub-packages of model, view and presenter under the module. Of course, model, view and presenter can also be used as top-level packages. Then all the module models, view and presenter classes are added to these three top-level packages, as if someone likes to put the project in place. Some activities, fragments and adapters are all put together.

Let's first look at LoginActivity

public class LoginActivity extends ActionBarActivity implements ILoginView, View.OnClickListener {

    private EditText editUser;
    private EditText editPass;
    private Button   btnLogin;
    private Button   btnClear;
    ILoginPresenter loginPresenter;
    private ProgressBar progressBar;

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

        //find view
        editUser = (EditText) this.findViewById(R.id.et_login_username);
        editPass = (EditText) this.findViewById(R.id.et_login_password);
        btnLogin = (Button) this.findViewById(R.id.btn_login_login);
        btnClear = (Button) this.findViewById(R.id.btn_login_clear);
        progressBar = (ProgressBar) this.findViewById(R.id.progress_login);

        //set listener
        btnLogin.setOnClickListener(this);
        btnClear.setOnClickListener(this);

        //init
        loginPresenter = new LoginPresenterCompl(this);
        loginPresenter.setProgressBarVisiblity(View.INVISIBLE);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_login_clear:
                loginPresenter.clear();
                break;
            case R.id.btn_login_login:
                loginPresenter.setProgressBarVisiblity(View.VISIBLE);
                btnLogin.setEnabled(false);
                btnClear.setEnabled(false);
                loginPresenter.doLogin(editUser.getText().toString(), editPass.getText().toString());
                break;
        }
    }

    @Override
    public void onClearText() {
        editUser.setText("");
        editPass.setText("");
    }

    @Override
    public void onLoginResult(Boolean result, int code) {
        loginPresenter.setProgressBarVisiblity(View.INVISIBLE);
        btnLogin.setEnabled(true);
        btnClear.setEnabled(true);
        if (result){
            Toast.makeText(this,"Login Success",Toast.LENGTH_SHORT).show();
            startActivity(new Intent(this, HomeActivity.class));
        }
        else
            Toast.makeText(this,"Login Fail, code = " + code,Toast.LENGTH_SHORT).show();
    }


    @Override
    public void onSetProgressBarVisibility(int visibility) {
        progressBar.setVisibility(visibility);
    }
}

From the code, we can see that LoginActivity only does the work of findView and setListener, and contains an ILoginPresenter. All business logic is completed by calling the specific interface of ILoginPresenter. So the code for LoginActivity looks comfortable, even a little pleasant (/ _ *). You may also see the implementation of the ILoginView interface with good vision. If you don't understand why you want to write this, you can look down first. Just remember that LoginActivity implements the ILoginView interface.

Take another look at ILogin Presenter

public interface ILoginPresenter {
    void clear();
    void doLogin(String name, String passwd);
    void setProgressBarVisiblity(int visiblity);
}
public class LoginPresenterCompl implements ILoginPresenter {
    ILoginView iLoginView;
    IUser user;
    Handler    handler;

    public LoginPresenterCompl(ILoginView iLoginView) {
        this.iLoginView = iLoginView;
        initUser();
        handler = new Handler(Looper.getMainLooper());
    }

    @Override
    public void clear() {
        iLoginView.onClearText();
    }

    @Override
    public void doLogin(String name, String passwd) {
        Boolean isLoginSuccess = true;
        final int code = user.checkUserValidity(name,passwd);
        if (code!=0) isLoginSuccess = false;
        final Boolean result = isLoginSuccess;
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                iLoginView.onLoginResult(result, code);
            }
        }, 3000);

    }

    @Override
    public void setProgressBarVisiblity(int visiblity){
        iLoginView.onSetProgressBarVisibility(visiblity);
    }

    private void initUser(){
        user = new UserModel("mvp","mvp");
    }
}

As you can see from the code, LoginPresenterCompl retains the reference to ILoginView, so you can do UI operations directly in LoginPresenterCompl instead of in Activity. The ILoginView reference is used instead of using Activity directly, so that if the same business logic is needed in other activities, the LoginPresenterCompl class can be reused directly (an activity can contain more than one Presenter, in short, whatever business is needed is what new Presen is. Ter, is it very flexible? This is the core idea of MVP.

Through IVIew and IPresenter, the UI Logic and Business Logic of Activity are separated. Activity just does its basic job! As for Model, it is still the model in the original MVC.

Look at ILoginView again. As for the implementation class of ILoginView, look at LoginActivity above.

public interface ILoginView {
    public void onClearText();
    public void onLoginResult(Boolean result, int code);
    public void onSetProgressBarVisibility(int visibility);
}

Code, this kind of thing in the log, seems to be useless except to lengthen the whole page. I have written several MVP s that I often use as a Demo project. Welcome the onlookers and Pull Request: Android-MVP-Pattern.

Epilogue

This is my understanding of MVP mode. Now that MVVM mode is not mature, I think there is no better alternative than MVP mode. Of course, today's writing is only the basic use of MVP, the example project introduced is very simple, do not see the advantages of MVP, and later will write some logs for MVP mode, as far as I can think of at least including

  • Android's regular development model is often called MV-View, which introduces data-bound MVVM (Model-View-View Model), which is different from MVP.

  • At present, we like to write ListView's Adapter as an internal class. It's difficult to use the same Adapter in two activities. It's easy to reuse the Adapter through MVP mode.

  • MVP mode needs to write many new interfaces, which is also its shortcoming. After a period of actual combat, I have an optimized MVP mode myself. I will try to summarize it and take her out to talk about it.


  1. I've also been confused about the MVP model or MVP structure, which is more accurate. The popular name abroad is Android MVP directly. Besides MVP Pattern s, MVP Framework/Architecture is also called. I think it should be a Code Style. It should be similar to Design Pattern in classification. Esign pattern), so now I'm generally called pattern, but that's not the point, is it? ()

Implementing login function with MVP mode

model layer

1,IUser

public interface IUser {
    public String getUsername();
    public String getPWD();
    public boolean checkLoginVisible(String username,String pwd);
}

2,UserModel

public class UserModel implements IUser{

    private String username;
    private String pwd;

    public UserModel(String username,String pwd){
        this.username=username;
        this.pwd=pwd;
    }

    @Override
    public String getUsername() {
        // TODO Auto-generated method stub
        return username;
    }

    @Override
    public String getPWD() {
        // TODO Auto-generated method stub
        return pwd;
    }

    @Override
    public boolean checkLoginVisible(String username, String pwd) {
        // TODO Auto-generated method stub
        if(this.username.equals(username)&&this.pwd.equals(pwd)){
            return true;
        }
        return false;
    }
}

presenter layer

1,ILoginPresenter

public interface ILoginPresenter {
    public void clear();
    public void doLogin(String user, String pwd);
    public void setProgressBarVisiblity(int visiblity);
}

2,LoginPresenterCompl

public class LoginPresenterCompl implements ILoginPresenter {
    private ILoginView iLoginView;
    private IUser iUser;
    Handler handler;

    public LoginPresenterCompl(ILoginView iLoginView) {
        this.iLoginView = iLoginView;
        initUser();
        handler = new Handler(Looper.getMainLooper());
    }

    private void initUser() {
        iUser = new UserModel("mvp", "mvp");

    }

    @Override
    public void clear() {

        iLoginView.onClearText();

    }

    @Override
    public void doLogin(String user, String pwd) {

        final boolean code = iUser.checkLoginVisible(user, pwd);
        if (code){

        handler.postDelayed(new Runnable() {

            @Override
            public void run() {
                iLoginView.LoginResult(code);
            }
        }, 3000);
        }
    }

    @Override
    public void setProgressBarVisiblity(int visiblity) {
        iLoginView.onSetProgressBarVisibility(visiblity);

    }

}

view

1,ILoginView

public interface ILoginView {
    public void onClearText();

    public void LoginResult(boolean result);

    public void onSetProgressBarVisibility(int visibilty);
}

2. LoginActivity calls presenter when it needs logical processing to execute its life cycle, and view when it needs view processing. But in order to facilitate the implementation of view interface, Activity also assumes the function of view, but in the actual development of Activity and view can be separated...

public class LoginActivity extends Activity implements ILoginView,
        OnClickListener {
    private EditText editUser;
    private EditText editPass;
    private Button btnLogin;
    private Button btnClear;
    private ProgressBar progressBar;

    private LoginPresenterCompl loginPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        setContentView(R.layout.login_main);

        editUser = (EditText) this.findViewById(R.id.et_username);
        editPass = (EditText) this.findViewById(R.id.et_password);
        btnLogin = (Button) this.findViewById(R.id.btn_login);
        btnClear = (Button) this.findViewById(R.id.btn_clear);
        progressBar = (ProgressBar) this.findViewById(R.id.progress_login);

        btnLogin.setOnClickListener(this);
        btnClear.setOnClickListener(this);

        loginPresenter=new LoginPresenterCompl(this);


    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
        case R.id.btn_login:

            loginPresenter.setProgressBarVisiblity(View.VISIBLE);
            loginPresenter.doLogin(editUser.getText().toString(), editPass
                    .getText().toString());
            break;
        case R.id.btn_clear:
            loginPresenter.clear();
           break;
        }
    }

    @Override
    public void LoginResult(boolean ret) {

              btnClear.setEnabled(true);
              btnLogin.setEnabled(true);
              loginPresenter.setProgressBarVisiblity(View.VISIBLE);
              if(ret)
              {
                  Toast.makeText(this, "Successful login", Toast.LENGTH_SHORT).show();
                  startActivity(new Intent(this,NotificationActivity.class));
          }else{
              Toast.makeText(this, "Logon failure", Toast.LENGTH_SHORT).show();
          }

    }

    @Override
    public void onClearText() {
        editPass.setText("");
        editUser.setText("");

    }

    @Override
    public void onSetProgressBarVisibility(int visibilty) {
        progressBar.setVisibility(visibilty);
    }

}

Keywords: Android Fragment Java less

Added by php_blob on Fri, 12 Jul 2019 02:37:32 +0300