DEV Community

Cover image for Clean Design using MVP
Subbu Lakshmanan
Subbu Lakshmanan

Posted on • Updated on

Clean Design using MVP

MVP

What is MVP?

Clue: It is not Most Valuable Player. or Most Valuable Primate

Model-View-Presenter is an architecural design pattern that's been getting lots of traction in android app development community.

Why should you use MVP?

Rather than copying and pasting the awesomeness of MVP design pattern for android, I will let you to jump into the links that I found most useful.

  1. MVP Understanding
  2. MVP Guidelines
  3. MVP Part-1
  4. MVP Part-2
  5. MVP Part-3

How did I get started?

As I mentioned earlier, there were several articles about MVP in addition to the links that I mentioned above. After reading through several of them, I realized that MVP offers much cleaner implmentation because of the fact that it embraces the Single Responsibility Principle by separating the UI from the actual implementation. And also I had been facing hard time writing unit test cases for some functionalities in our app that were tightly coupled with UI. They kept breaking the Unit test cases and I had hard time debugging the test cases. I decided to give it a shot to MVP design pattern and sweet, it provided everything what I was looking for.

Note: This is only my second implementation in MVP design. There my be some errors or misunderstanding of the concepts.

In my opinion, the question you should as yourself before implementing in MVP are,

  1. Should I use progress bar or progress wheel?
  2. What are the colors?
  3. Should I use AsyncTask or Handlers for longer running process?
  4. Should I do..

no. No. NO. NO!

Trust me! I know it is very easy to think about and get carried away into the technical details of implementation. However We have to change the thinking.

  • Think only about purpose of the screen.
  • Forget every techincal details involved.

The functionality I chose to refactor in our app was Login and initial boot up screen. The responsiblities of the screen were,

  1. Login the user with the Authentication Micro service
  2. Register the device to Servers,
  3. Sync the contacts using Sync Service,
  4. Sync User Voicemails through Voicemail Sync Service

Designing the Model

When you start designing the Model interface, do not worry about the technical details and concentrate only on the functionality that you wanna achieve.

This was the initial sketch of the interface I started with.

interface LoginModel {

    void initLogin();

    void completeLogin();

    void initRegistrationProcess();

    void completeRegistrationProcess();

    void initContactsSync();

    void initVoicemailSync();
}
Enter fullscreen mode Exit fullscreen mode

As these operations may take longer time to complete and also to reduce the dependency, the Model interface defines a Listener interface to pass down the results.

interface LoginModel {

    interface LoginModelListener {

        void onConfigurationFailure();

        void onLoginSuccess();

        void onLoginFailure();

        void onSyncFailure();
    }

    void initLogin();

    void completeLogin();

    void initRegistrationProcess();

    void completeRegistrationProcess();

    void initContactsSync();

    void initVoicemailSync();
}
Enter fullscreen mode Exit fullscreen mode

Note: Ideally you can design the Listener interface methods to pass the results as parameters, however we care only about the results of operations here.

Since the Model class contains only the functionality and doesn't hold any dependency on the User interface, its quite easy to write Android Unit Test cases to verify the functionality as below,

public class LoginModelImplTest implements LoginModel.LoginModelListener {

    private LoginModelImpl mLoginModel;

    @Before
    public void setUp() throws Exception {
        mLoginModel = new LoginModelImpl(this);
    }

    @Test
    public void testLogin() throws Exception {
        // TODO: 5/24/17 Add implementation
    }

    @After
    public void tearDown() throws Exception {

    }

    @Override
    public void onLoginSuccess() {
        // TODO: 5/24/17 Add implementation
    }

    @Override
    public void onLoginFailure() {
        fail();

    }

    @Override
    public void onSyncFailure() {
        // TODO: 5/24/17 Add implementation
    }
}
Enter fullscreen mode Exit fullscreen mode

Now you have the 'Model' interface to define the functionality and a unit test case class to verify it.

Designing the View

When designing the view, think about what information to show, rather than how to show it. For example, here the information interesting to the users are,

  • Results of the each step in the process.
  • Errors in case something went wrong.

This information can be shown with any UI elements.

interface LoginView {

    void updateStatus(int step, boolean status);

    void showProgress();

    void hideProgress();

    void showError(String errorCode);
}
Enter fullscreen mode Exit fullscreen mode

Notice the methods doesn't actually care about the UI elements that can be used. Based on the requirements, you can have any different types of user interfaces.
For example, here is the simple implementation of the LoginView

App Screenshot

The simplest implementation took about than 100 lines of code, where as the implementation can use any UI View components.

Designing the Presenter

The presenter interface acts as the bridge between the View and Model. So it will have methods to invoke functionalities defined by the Model and also methods to handle the data as the lifecycle of the UI changes.

interface LoginPresenter {

    // To invoke Model functionalities
    void initLogin();

    void initRegistration();

    void initContactsSync();

    void initVoicemailSync();

    // To track UI lifecycles
    void onCreate();

    void onResume();

    void onPause();

    void onDestroy();
}
Enter fullscreen mode Exit fullscreen mode
class LoginPresenterImpl implements LoginPresenter, LoginModel.LoginModelListener {

    private final LoginView mLoginView;
    private final LoginModel mLoginModel;

    LoginPresenterImpl(LoginView loginView) {
        this.mLoginView = loginView;
        this.mLoginModel = new LoginModelImpl(this);
    }

    ...
}
Enter fullscreen mode Exit fullscreen mode

Notice that the 'Presenter' implementation has,

  1. Reference to the Model: Used to call methods defined in Model
  2. Reference to the View: Used to update the UI
  3. Implements the Listener interface: Get results back from Model

Now the implementation of 'Presenter' becomes very simple.

  1. Call the 'Model' methods,
  2. Get the results back using the 'Listener',
  3. Update the 'View'.
class LoginPresenterImpl implements LoginPresenter, LoginModel.LoginModelListener {
    ...

    @Override
    public void initLogin() {
        mLoginModel.initLogin();
    }

    @Override
    public void onLoginSuccess() {
        mLoginView.updateStatus(0, true);

        mLoginModel.completeLogin();

        initRegistration();
    }

    @Override
    public void onLoginFailure() {
        mLoginView.updateStatus(0, false);
        mLoginView.showError(ErrorCodes.LOGIN_FAILED);
    }

    @Override
    public void initRegistration() {
        mLoginModel.initRegistrationProcess();
    }

    ...
}
Enter fullscreen mode Exit fullscreen mode

Now you can implement the functionalities 'Model' interface, test with Unit Test cases. Once you verified the functionalities, you can tie up the Presenter and View. The 'View' implementation can be as simple as console outputs or complex UI components.

The major advantages of this approach that I found are,

  1. When the User interface requirements changes, all you have to do is to change the UI components.
  2. When the functionality is required in another place, it can be achieved by creating a new fragment/activity that implements the View interface.
  3. If one of the functionality changes from one to another, for ex., Couchbase to Websockets, only the implementation detail changes; the functionality stays the same.
  4. It is very easy to do Test Driven Development. I will write down another blog about my TDD attempt using MVP design pattern. The simple steps are,
    1. Start with the 'Model' interface skeleton with methods.
    2. Write the Unit test cases for the methods.
    3. Implement the 'Model' interface and implement one method at a time and unit test the functionality.
    4. Once the Model class is complete, hook up the presenter and view.

I plan to use MVP design for developing new features and refactoring existing features and I hope to get a better understanding of the MVP design pattern. I will write another follow-up article if needed.

Known mistakes:
One of the guidelines mentioned in this article was, not to create lifecycle methods for Presenter interface. However the examples I have seen in several other places used some sort of lifecylce methods and it made sense to use them here.

Top comments (2)

Collapse
 
ben profile image
Ben Halpern

I'm glad you made reference to the other MVPs and I'll throw in Minimum Viable Product as another source of confusion. Great post breaking down the concepts, but these three-letter acronyms will be the death of me!

Collapse
 
subbramanil profile image
Subbu Lakshmanan

Ha Ha!. I forgot that one even though we stress on Minimum Viable Product, every now and then at my work. Appreciate your Great work in the dev.to() => Practical Dev. I love the blogging style with simple markdown.