Recently, because of the perceived differences between my colleagues and my perception of MVP, I took a second look at the way MVP is written, reflected on some of the issues that have arisen in the current project, and created a variation of MVP that I’ll call MCVP. There’s a demo at the end.

MVP

Those of you who are not interested in the basics can jump right into the MCVP section.

MVP is one of the solutions to the problems posed by the traditional Android development architecture MVC: In MVC architecture, Activity acts as both View and Controller, and finally becomes God class and the number of class code explodes. Even if the Controller class is separated separately, the situation is still not much changed, just a new God. This is when MVP is recommended, which decouples Activity (View) and Controller (Presenter) in the original sense by following the dependency inversion principle, separating and reusing our UI and business logic through interface abstraction.

Here’s a picture of the MVP pattern everywhere, to reiterate the basic MVP concept:

Views generally refer to activities and fragments, which host the USER’s UI interface and interact directly with the user.

Model refers to a data source, which is a collection of objects responsible for retrieving and storing data, rather than a specific entity or interface called Model. It is more appropriately described in terms of layers — the Model layer.

Presenter is the coordinator between the View and the Model, handling and accepting business logic.

A View and Presenter are decoupled by interface calls. Methods on an interface should be an abstraction of their behavior: as callers to call each other’s methods, they have an intent: I want to do; And the exposed method of the called is the set of what I can do. The caller doesn’t need to care about what’s going on internally. Should the Model use interfaces for abstraction? I don’t think this is necessary in most applications. The Model interface is expensive to maintain and adds little value unless you really have multiple implementations of your data source, such as using different server databases in different situations, consider interface decoupling.

The MVP mode is written in two ways: Passive View and Supervising Controller. In the passive View, the View gives the Presenter control over its logic and interaction with the Model, and even its own presentation. Presenter is only a logical aid in supervising the controller, and the View will have partial access to the data modified to the Model, but I think this is unavoidable for passive views. For example, listing always relies on entity classes, so I prefer supervising the controller.

The above is just my personal understanding, welcome to discuss.

A question that often arises

Many people don’t understand MVP or even the basic principles of object orientation well enough, which leads to a lot of weird questions, including but not limited to the following:

  1. Create an IModel interface, implement a Model class for each different IPresenter interface, and Contract the IPresenter and IModel together. This not only creates a lot of Model classes, but also creates a lot of redundant code. As I said above, the Model is actually a collection, a Model layer concept. This graph is even more illustrative.

  1. The same problem as in point 2 is that there is no abstraction between View and Presenter behavior. Presenter is too involved in controlling the display of the View, violating the principle of single responsibility, and even performing direct operations on certain View controls. Interfaces expose only the abstraction of behavior, and the specific operational details should not be exposed.

  2. All click events of a View are passed to a particular method with a unique argument to Presenter, where Presenter performs the judgment on the argument set. This is also the case without abstracting the behavior. View is like a walking dead, with no intention of “I want to do” at all. It is like throwing a rock into a black hole. What happens inside the black hole? I don ‘t know. Think about it. View-ui is a platform for interaction with the user. When the user clicks on an icon from the UI, does the user have an intention? So should UI events have intent?

  1. Presenter knows about the Android framework. The data acquisition and delivery of intEnts and the return handling of onActivityResult() are part of the Android framework and should be handed back to the View. For example, a Presenter should not rely on the Android environment for testing. For example, an Intent data is defined as an activity, and startActivity() is a fragment.

MCVP

There are a lot of strange things mentioned above but they don’t necessarily have problems in your project, actually they are not the point, the next problem I believe has occurred in your project, or you have seen the project.

Scenarios and Requirements: the page for submitting orders will prompt you to upgrade your order after submitting the order. After upgrading, you can get better after-sales service, but a small amount of service fee will be charged. Whether the order can be upgraded is dynamically configured by the background.

The process is shown in the figure:

The code looks something like this:

public interface SubmitOrderContract {        
    interface IView {        
        void askUserForUpgrade(CallBack<Boolean> callback)void onSubmitSucceed(a)void onSubmitFailed(a);   
    }        
    
    interface IPresenter {        
        /** * submit the order */        
        void submitOrder(SubmitOrderEntity order); }}Copy the code
public class SubmitOrderActivity extends AppCompatActivity implements SubmitOrderContract.IView {...@Override    
    public void askUserForUpgrade(final CallBack<Boolean> callback) final InquiryDialog dialog = new InquiryDialog(this); 
        dialog.setOnResultListener(new InquiryDialog.OnResultListener({ 
            @Override 
            public void onResult(boolean result) { dialog.dismiss(); callback.onResult(result); }}); dialog.show(); }... }Copy the code
public class SubmitOrderPresenter implements 
SubmitOrderContract.IPresenter {...@Override
    public void submitOrder(final SubmitOrderEntity order) {  
        mApi.submitOrder(order, new HttpApi.HttpListener<SubmitResultEntity>() { 
            @Override
            public void onCallBackOk(SubmitResultEntity result) {
                if (result.canUpDate) {
                    mView.askUserForUpgrade(new CallBack<Boolean>() { 
                        @Override
                        public void onResult(Boolean p) { submitOnUpgrade(p, order); }}); }else{ mView.onSubmitSucceed(); }}}); }private void submitOnUpgrade(boolean isContinue, SubmitOrderEntity order) {
        if(! isContinue) { mView.onSubmitFailed();return;
        } 
        mApi.submitOrderForUpgrade(order, new HttpApi.HttpListener<SubmitResultEntity>() {
            @Override
            public void onCallBackOk(SubmitResultEntity submitResultEntity) { mView.onSubmitSucceed(); }}); }}Copy the code
Problems that arise

There are two problems with this:

  1. Business logic flow fragmentation. Our business code is broken into submitOrder() and submitUpgradeOrder() methods due to the need for View’s pop-up query, resulting in logical incoherence.

  1. Logic leakage. SubmitUpgradeOrder () is a step in the order submission process that the Presenter has no idea when it will trigger. SubmitUpgradeOrder () holds the trigger logic in the View, and the View holds the business logic!

The solution

For this particular scenario, my solution is as follows:

MCVP MCVP MCVP MCVP MCVP

The Model – CallbackView – Presenter. If you look at the word CallbackView, you might have guessed the answer, yes, it means a View that responds. The MCVP is a special variant of the MVP that can be applied to your project at any time.

In a Presenter’s perspective, asking a user whether to upgrade an order is nothing more than an asynchronous process of retrieving data. We can think of a View as a data source, waiting for the result of a user’s choice to be returned in the same manner as a network request.

It is enough for the View to ask the user to confirm and then tell the Presenter, without even knowing what the Presenter wants it to ask the user to confirm. Just like the relationship between the vegetable vendor and the customer, the vegetable vendor only needs to know what vegetables the customer needs, but does not need to know what the customer buys the melon to do, complete decoupling.

When we choose this option, the input parameter passed to the View becomes a Callback interface with a generic type. The View only knows what it needs to answer (the generic T), and it calls back through the Callback interface.

advantages

MCVP completely avoids business logic fragmentation and keeps the flow coherent. Presenter is completely knowable to the logic flow, which improves the readability of the logic. The business logic methods in the class are more cohesive, and your lovely business logic does not need to jump between View and Presenter implementations. People who are new to the code understand the logic flow easily, and maintainability is improved. And that’s exactly how passive views are written, so the View doesn’t touch any entity classes. The two problems described above are solved.

Take a look at the MCVP code:

public interface SubmitOrderContract {        
    interface IView {        
        void askUserForUpgrade(CallBack<Boolean> callback)void onSubmitSucceed(a)void onSubmitFailed(a);   
    }        
    
    interface IPresenter {        
        /** * submit the order */        
        void submitOrder(SubmitOrderEntity order); }}Copy the code
public class SubmitOrderActivity extends AppCompatActivity implements SubmitOrderContract.IView {...@Override    
    public void askUserForUpgrade(final CallBack<Boolean> callback) final InquiryDialog dialog = new InquiryDialog(this); 
        dialog.setOnResultListener(new InquiryDialog.OnResultListener({ 
            @Override 
            public void onResult(boolean result) { dialog.dismiss(); callback.onResult(result); }}); dialog.show(); }... }Copy the code
public class SubmitOrderPresenter implements SubmitOrderContract.IPresenter {...@Override
    public void submitOrder(final SubmitOrderEntity order) {
        mApi.submitOrder(order, new 
        HttpApi.HttpListener<SubmitResultEntity>() {
            @Override
            public void onCallBackOk(SubmitResultEntity result) {
                if (result.canUpDate) {
                    mView.askUserForUpgrade(new CallBack<Boolean>() {
                        @Override
                        public void onResult(Boolean p) { submitOnUpgrade(p, order); }}); }else{mView.onSubmitSucceed(); }}}); }private void submitOnUpgrade(boolean isContinue, SubmitOrderEntity order) {
        if(! isContinue) { mView.onSubmitFailed();return;
        }
        mApi.submitOrderForUpgrade(order, new HttpApi.HttpListener<SubmitResultEntity>() {
            @Override
            public void onCallBackOk(SubmitResultEntity submitResultEntity) { mView.onSubmitSucceed(); }}); }}Copy the code

As you can see, all the business processes are in submitOrder() without breaking out of this method, and submitOrder() faithfully completes its task of submitting the order without any further manipulation of the View.

That’s easy to solve, I believe we all know the solution, Rxjava, coroutine… They can all be solved. For reasons of space, the RxJava code section can be seen at the end of the portal to see the demo.

disadvantages

MCVP has its advantages, but I also admit that it has its drawbacks:

  1. Callback can only be used once, and make sure it doesn’t get called twice or your logic will go through twice, but as long as you keep in mind that this is the case, there will be very little trouble.
  2. Because Callback exists, the View needs to hold the Callback object, either as a global variable (just one) or each time you create your UI (as in the dialog example), but I don’t think this is really a problem. The global variable is a minor impurity that doesn’t affect its benefits.

MCVP is nothing more than a combination of CallbackView and MVP, and the idea is perfectly portable to other modes. In addition to my examples, there should be more ways to write it, such as returning a Callback from a View method rather than presenting a Callback, and I will continue to think about optimizing MCVP in more scenarios to make our code structure better.

Go to dead simple to see the Demo

Thanks for watching!

Watch picachau this weekend ~!