MVP in Android

5.00 avg. rating (95% score) - 1 vote

It is commonly said that Unit Testing in Android is hard and we observed the same. This is mainly because the standard way of developing Android applications encourages us to write code that is difficult to unit test. A lot of business logic resides in the component classes (e.g. Activity, Fragments, Services etc..) which is definitely not easy to test. We can write UI tests for them but these tests are based on instrumentation API and are very slow as they run on an emulator/device and are more like end to end functional tests rather than unit tests.

Version 1.1 of Android Studio added great support for unit testing. So, if the unit tests do not have or have simple dependencies on Android API, we can run them on local develpment machines whithout the need to load the application onto a device or emulator. These are local JUnit tests that execute on the JVM. As a result, test execution becomes much faster.

So, the views were complex and unit testing was hard. We were looking at ways to enhance testability of apps by restructuring them to move business logic into separate business objects and by using principles like Dependency Injection. That is when we came across MVP.

Most of the android applications are built using the well known MVC pattern. As more and more features are added, the View classes becomes complex and testing the UI logic becomes difficult. MVP (Model-View-Presenter) is a pattern that dissociates the UI concerns from the presentation logic which allows for more modular code and enables easy unit testing of presentation logic.

The three elements of MVP are,

  1. Model: implements business logic to fetch and save data and any other logic that the data must adhere to.
  2. View: accepts user input and updates the display.
  3. Presenter: handles communication between the Model and View. It is a business object that contains the business logic that used to reside in Activity/Fragment.

An example

Just to illustrate how we migrated our code to MVP, consider a simple Registration form with two fields viz. email and password. Let us see how it can be modelled using MVC and MVP. For brevity, only relevant code extracts are shown below. The complete source is available on github.

MVC:

public class Registration extends AppCompatActivity {   

    public void onRegisterClicked(View view) {
        String email = this.email.getText().toString();
        String password = this.password.getText().toString();
        if (email.isEmpty()) {
            this.email.setError(getString(R.string.username_error));
            return;
        }
        if (password.isEmpty()) {
            this.password.setError(getString(R.string.password_error));
            return;
        }
        boolean registerSuccess = new RegistrationServiceManager().register(email, password);
        if (registerSuccess) {
            startActivity(new Intent(this, MainActivity.class));
        } else {
            Toast.makeText(this, getString(R.string.registration_failed), Toast.LENGTH_SHORT).show();
        }
    }
}

The Activity class accepts user inputs, validates them, calls the web-service and updates view for success or failure as the case may be. As we see, one single Activity class has too many responsibilities which makes it too complex. Also, the business logic of validating inputs cannot be tested independent of the View.

MVP:

Applying MVP to the Registration example above, we create a standard Java class called Presenter. We have taken out most of logic from the Activity into this class.

public class RegistrationPresenter {
    
    public RegistrationPresenter(RegistrationView registrationView, RegistrationServiceManager registrationServiceManager) {
        this.registrationView = registrationView;
        this.registrationServiceManager = registrationServiceManager;
    }

    public void onRegistrationClicked(String email, String password) {
        if (email.isEmpty()) {
            registrationView.showEmailError(R.string.username_error);
            return;
        }
        if (password.isEmpty()) {
            registrationView.showPasswordError(R.string.password_error);
            return;
        }
        boolean registrationSuccess = registrationServiceManager.register(email, password);
        if (registrationSuccess) {
            registrationView.startMainActivity();
            return;
        }
        registrationView.showError(R.string.registration_failed);
    }
}

Presenter does not need to know who the View is, and because of this, we can extend, refactor and test the Presenter logic independently and without the need of instrumentation api.

public class Registration extends AppCompatActivity implements RegistrationView {   
    public void onRegisterClicked(View view) {
        String email = this.email.getText().toString();
        String password = this.password.getText().toString();
        registrationPresenter.onRegistrationClicked(email, password);
    }
    @Override
    public void showEmailError(int resId) {
        email.setError(getString(resId));
    }
    @Override
    public void showPasswordError(int resId) {
        password.setError(getString(resId));
    }
    @Override
    public void startMainActivity() {
        startActivity(new Intent(this, MainActivity.class));
    }
    @Override
    public void showError(int resId) {
        Toast.makeText(this, getString(resId), Toast.LENGTH_SHORT).show();
    }
}

The View now just displays the information passed to it from Presenter. Button clicks are passed over to Presenter to handle them. Afterwards Presenter calls the appropriate methods in View. The View now becomes almost dumb.

What we now have is a completely testable Presenter class. The validation logic which was earlier difficult to test has now become straightforward for testing. A simple test case could be,

@Test
public void shouldShowErrorMessageWhenEmailIsInvalid() {
    String email = "abc";
    String password = "123";
    registrationPresenter.onRegistrationClicked(email, password);
    verify(registrationView).showEmailError(R.string.email_error);
}

To fulfill the dependency relationships, we created Mocks using a popular framework called Mockito.

Impact

Ever since we adopted MVP to structure our Android apps, we have realized the following benefits,

  1. Better code organization with well separated layers
  2. Cleaner, modular and more readable code.
  3. Completely testable and reusable Presenter layer.
  4. The unit test coverage for views has increased by 40% which is a major breakthrough.
  5. Complex Activity classes which were closely coupled with interface and data now become very simple.

By its own very nature, separating interface from logic in Android is not trivial, but the Model-View-Presenter pattern makes it easy to. MVP encourages separation of concerns and makes the code easy to test, maintain and extend.

Posted in Mobile