Get started with Android MVP in 10 minutes!

foreword

In the process of daily APP development, as the business expands, the scale changes. The size of our code will gradually become larger, and the code in each class will gradually increase. Especially Activity and Fragment, due to the existence of Context, basically all operations on the view can only be done in Activity and Fragment; even if some logic is encapsulated, Activity and Fragment will still be too bloated. Therefore, we need to write code in a different way. At this time, the MVP pattern is applied! So how to use MVP, let's talk about it.

Suppose you now want to implement the function in the following figure:

This requirement is very simple, just click the button, download an image, and display the download progress; after the download is complete, display the image in ImageView.
Next, we will use the traditional way (that is, the so-called MVC) and the MVP model to achieve this function respectively. Then analyze what is good about MVP.

MVC

public class MVCActivity extends AppCompatActivity {
    
    

    private Context mContext;
    private ImageView mImageView;
    private MyHandler mMyHandler;
    private ProgressDialog progressDialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mvc);
        mContext = this;
        init();
    }

    private void init() {
        //view init
        mImageView = (ImageView) findViewById(R.id.image);
        mMyHandler = new MyHandler();

        progressDialog = new ProgressDialog(mContext);
        progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancle", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                progressDialog.dismiss();
            }
        });
        progressDialog.setCanceledOnTouchOutside(false);
        progressDialog.setTitle("下载文件");
        progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);


        //click-event
        findViewById(R.id.downloadBtn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                progressDialog.show();
                HttpUtil.HttpGet(Constants.DOWNLOAD_URL, new DownloadCallback(mMyHandler));
            }
        });

        findViewById(R.id.downloadBtn1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                progressDialog.show();
                HttpUtil.HttpGet(Constants.DOWNLOAD_ERROR_URL, new DownloadCallback(mMyHandler));
            }
        });

    }


    class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 300:
                    int percent = msg.arg1;
                    if (percent < 100) {
                        progressDialog.setProgress(percent);
                    } else {
                        progressDialog.dismiss();
                        Glide.with(mContext).load(Constants.LOCAL_FILE_PATH).into(mImageView);
                    }
                    break;
                case 404:
                    progressDialog.dismiss();
                    Toast.makeText(mContext, "Download fail !", Toast.LENGTH_SHORT).show();
                    break;
                default:
                    break;
            }
        }
    }
}

In the mvc way, an Activity can be done. The code logic is very simple. After clicking the button, the ProgressDialog is initialized before the display, and then the download task is started (here, HttpUtil simply encapsulates the asynchronous GET request of OKHttp, and realizes the function of saving the downloaded file to the local. The implementation details are not discussed in depth here. There are Interested students can view the source code ), and then return the request result through Handler, and make different UI processing in handleMessage according to the information of the returned data; when the download is successful, the image is displayed in the ImageView, and the Toast prompts when the download fails.

It can be found that before this situation, the task of Activity is very heavy, not only responsible for the specific implementation of the download task, but also to make another logical judgment according to the download, in order to update the UI. This is just a simple task, you may think it doesn't matter, but in actual development, there are many interactive events in an Activity. At this time, the code of the Activity is particularly huge; once the requirements change or bugs appear, it is simply a nightmare .

Therefore, we hope that the Activity can become the following

  • He is responsible for the content of initiating processing and user interaction, but is not responsible for the specific implementation;
  • What needs to be displayed, what not to be displayed, how much to be displayed, there is something that can tell him directly,
  • Activity no longer does complex logic processing;

Specifically in the demo above, Activity is responsible for initiating the download task, but not responsible for the specific implementation; when will the ProgressDialog be displayed and how much? When the error message is prompted, all of these hope that there is something that can tell the Activity directly, instead of making judgments in the Activity. How can this be done? Then it's up to the MVP.

MVP

What the MVP pattern does is simply abstract business logic and view logic into interfaces.

How to understand it, we will use the code to speak according to the download function to be implemented this time.

Define Model, View, Presenter interfaces

Model Interface

The Model interface defines all the business logic that needs to be implemented. In our download task, there is only one business logic, which is download; therefore, the Model interface can be defined as follows:

public interface IDownloadModel {
    
    
    /**
     * 下载操作
     * @param url
     */
    void download(String url);
}

View Interface

The View interface defines all the view logic that needs to be implemented. In our download task, the view logic includes
- display the ProgressDialog;
- display the specific progress of the Dialog;
- display the specific View (set the picture);
- display the error message (Toast prompt)

So the View interface can be defined like this:

public interface IDownloadView {
    
    
    /**
     * 显示进度条
     * @param show
     */
    void showProgressBar(boolean show);

    /**
     * 设置进度条进度
     * @param progress
     */
    void setProcessProgress(int progress);

    /**
     * 根据数据设置view
     * @param result
     */
    void setView(String result);

    /**
     * 设置请求失败时的view
     */
    void showFailToast();
}

Presenter Interface

The Presenter interface acts as an intermediate bridge between the Model and the View. It needs to connect the two. Therefore, it needs to complete the following tasks:
- Execute the download task
- Return the download result when the download is successful
- Return the download progress during the download process
- Download the download failure callback

Therefore, Presenter can be defined like this:

public interface IDowndownPresenter {
    
    
    /**
     * 下载
     * @param url
     */
    void download(String url);

    /**
     * 下载成功
     * @param result
     */
    void downloadSuccess(String result);

    /**
     * 当前下载进度
     * @param progress
     */
    void downloadProgress(int progress);

    /**
     * 下载失败
     */
    void downloadFail();
}

Interface Model, View, Presenter concrete implementation

The above is implemented, the definition of each interface, let's take a look at their specific implementation:

Model specific implementation

public class DownloadModel implements IDownloadModel {
    
    
    private IDowndownPresenter mIDowndownPresenter;
    private MyHandler mMyHandler = new MyHandler();

    public DownloadModel(IDowndownPresenter IDowndownPresenter) {
        mIDowndownPresenter = IDowndownPresenter;
    }

    @Override
    public void download(String url) {
        HttpUtil.HttpGet(url, new DownloadCallback(mMyHandler));
    }

    class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 300:
                    int percent = msg.arg1;
                    if (percent < 100) {
                        mIDowndownPresenter.downloadProgress(percent);
                    } else {
                        mIDowndownPresenter.downloadSuccess(Constants.LOCAL_FILE_PATH);
                    }
                    break;
                case 404:
                    mIDowndownPresenter.downloadFail();
                    break;
                default:
                    break;
            }
        }
    }
}

In the MVP mode, the job of the Model is to complete tasks such as specific business operations, network requests, and persistent data additions, deletions, and changes. At the same time, the Model will not contain any View.
The specific implementation of the Model here is very simple. The result of the Http task is returned to the Handler, and the implementation in the Handler is completed by the Presenter.
So how is the Presenter interface implemented? come and see

Presenter concrete implementation

public class DownloadPresenter implements IDowndownPresenter {
    
    
    private IDownloadView mIDownloadView;
    private IDownloadModel mIDownloadModel;


    public DownloadPresenter(IDownloadView IDownloadView) {
        mIDownloadView = IDownloadView;
        mIDownloadModel = new DownloadModel(this);
    }

    @Override
    public void download(String url) {
        mIDownloadView.showProgressBar(true);
        mIDownloadModel.download(url);
    }

    @Override
    public void downloadSuccess(String result) {
        mIDownloadView.showProgressBar(false);
        mIDownloadView.setView(result);
    }

    @Override
    public void downloadProgress(int progress) {
        mIDownloadView.setProcessProgress(progress);
    }

    @Override
    public void downloadFail() {
        mIDownloadView.showProgressBar(false);
        mIDownloadView.showFailToast();
    }
}

It can be seen that in the construction method of DownloadPresenter, we instantiate Model and View at the same time, so that both are included in Presenter;
thus; **In the concrete implementation of Presenter, business-related operations are completed by Model (for example, download), view-related operations are done by View
(such as setView, etc.)**. The role of Presenter as a bridge is thus reflected, cleverly connecting the specific implementation of View and Model.

View specific implementation

Finally, let's take a look at the specific implementation of the View interface, that is, the implementation of Activity:

public class MVPActivity extends AppCompatActivity implements IDownloadView {
    
    
    private Context mContext;
    private ImageView mImageView;
    private ProgressDialog progressDialog;

    private DownloadPresenter mDownloadPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mContext = this;
        setContentView(R.layout.activity_mvp);
        init();
    }

    private void init() {
        mDownloadPresenter = new DownloadPresenter(this);
        //view init
        mImageView = (ImageView) findViewById(R.id.image);
        findViewById(R.id.downloadBtn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mDownloadPresenter.download(Constants.DOWNLOAD_URL);
            }
        });

        findViewById(R.id.downloadBtn1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mDownloadPresenter.download(Constants.DOWNLOAD_ERROR_URL);
            }
        });

        progressDialog = new ProgressDialog(mContext);
        progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancle", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                progressDialog.dismiss();
            }
        });
        progressDialog.setCanceledOnTouchOutside(false);
        progressDialog.setTitle("下载文件");
        progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);

    }

    @Override
    public void showProgressBar(boolean show) {
        if (show) {
            progressDialog.show();
        } else {
            progressDialog.dismiss();
        }
    }

    @Override
    public void setProcessProgress(int progress) {
        progressDialog.setProgress(progress);
    }

    @Override
    public void setView(String result) {
        Glide.with(mContext).load(result).into(mImageView);
    }

    @Override
    public void showFailToast() {
        Toast.makeText(mContext, "Download fail !", Toast.LENGTH_SHORT).show();
    }
}

When the button is clicked to start the download task, there is no specific implementation in the View (Activity), but the download method in the Presenter is called, and the download in the Presenter will call the download method of the Model, and the Model will call the download method according to the specific The state of the logic (here is the Http request) to call the method in the Presenter, for example, in the handleMessage method, when we call mIDowndownPresenter.downloadProgress(percent), we will call the specific implementation of the Presenter

    @Override
    public void downloadProgress(int progress) {
        mIDownloadView.setProcessProgress(progress);
    }

And his internal implementation is to operate the specific View, that is, the this passed in the Presenter we initialized in the Activity, which is the current Activity (View), which finally returns to the Activity.


    @Override
    public void setProcessProgress(int progress) {
        progressDialog.setProgress(progress);
    }

We set the progress for the progressDialog.

So far, we have implemented the Activity we envisioned before through the MVP model.

  • The click method of the Button is responsible for initiating the download task, but it is not responsible for the specific implementation, but is transferred by the Presenter to the Model for implementation
  • When the Activity displays the ProgressDialog and when the Toast is displayed, the Presenter tells him directly, he only does what a View wants to do
  • There is no logical processing in the Activity, and all logical judgments are completed in the Model.

This is the MVP! ! !

MVC VS MVP

Through the above two implementation schemes, I believe everyone has understood the difference between MVC and MVP; the following is a
summary ; of course, the advantages and disadvantages here are only relative .

advantage

MVC

MVP

The above two diagrams are MVC and MVP architecture diagrams respectively. I believe that many students who are trying to learn and understand the MVP architecture like me are not unfamiliar with these two diagrams (or similar diagrams).

Clearer structure*

Let's go back and look at the implementation of MVCActivity. For the time being, we boil down our encapsulation of Http requests to Model(M), then only Activity is left, and this Activity not only implements view logic, but also needs to implement some business logic. That is to say he is both Controller (C) and View (V). The division of V and C is completely unclear; therefore, the traditional code structure can only be called MV or MC, and if the layout file of xml is counted, it can be called the MVC structure.

The MVP is different. Model, View, and Presenter perform their own duties and match each other to achieve decoupling and completely liberate Activity (or Fragment). This is the advantage of MVP, the code structure is clearer. It can be said that the implementation of the same module even allows several people to complete the division of labor; if there is a very complex Activity, if it is developed using the MVP model; then at this time, after defining the MVP interface, someone can specialize in Modeling , another person specializes in View; another person writes the code of the Presenter, of course, this requires strong code specification and collaboration capabilities; but this is simply unimaginable in the traditional MVC pattern, everything is in one In the class, two people change together, how to play if there is a conflict /(ㄒoㄒ)/~~.

Requirements change, no longer a nightmare

Assuming that there are new requirements now, the product manager thinks that it is too monotonous to have only one Toast prompt after the download fails (and the user may miss the display of the Toast, and mistakenly think that the APP has lost response), so now I hope to pop up after the download fails. A Dialog to retry the download task. I think, if the code uses the traditional MVC structure, it happens that the code is not written by you, or you write it, but you have forgotten the specific logic; then in order to achieve this requirement, you have to re-examine the logic again, to Modify the xxx line of a certain class; but it is different if you use MVP. The View interface has been defined and showFailToast is used to display error prompts; so even if the code is not written by you, you can quickly find it, where should it go? change; and save a lot of time.

Easier to write unit tests

I won't go into this, in short, anyone who has written unit tests should have such an experience.

shortcoming

As good as MVP is, it's not without its flaws.

As shown in the figure, after using the MVP architecture, there are many more classes; this is inevitable; each View (Activity or Fragment) requires at least its own Model, Presenter and View interfaces, plus their respective implementations, That is to say, each page will have 6 java files (including Fragment or Activity, because it is the implementation of View), such a slightly scaled APP, the classes will become abnormally many, and the loading of each class It will consume resources again; therefore, compared to MVC, this is the biggest disadvantage of MVP.

Of course, for this problem, we can abstract some common Models and Presenters through generic parameters and abstract parent classes. This should be the essence of using the MVP architecture.

at last

Personally, the advantages of using the MVP architecture outweigh the disadvantages; as the scale of the project increases, the clarity of the code logic is the most important thing. Moreover, Google officially launched a series of demos on the use of MVP .
Therefore, this is also officially recommended for everyone to use. Everything has advantages and disadvantages; the growth of the number of classes is unavoidable, so how to use generics and abstractions to optimize the structure of MVP has become the
key to making good use of MVP.

Of course, we can't go for MVP for the sake of MVP. If the project structure is not very huge and the business is not very complicated; then the traditional MVC architecture is sufficient and convenient!


Guess you like

Origin blog.csdn.net/TOYOTA11/article/details/54728357