Android Code That Scales, With MVP

It’s always easy to write a Hello World application, the code looks simple and straight forward and the SDK feels very adapted to your needs. But if you have experience writing a more complex app on Android, you know that production code is not like this. You have spent hours trying to understand why your shopping cart won’t update after changing the orientation of the phone if there is no WiFi. You figured that the solution is probably to add yet another if statement to your already 457 lines long Activity’s onCreate() method, between the one that fixes that crash on Samsung devices running Android 4.1, and the one that shows a $5 coupon on the user’s birthday. Well, there is a better way.

At Remind, we ship new features every two weeks, and for us to maintain that velocity and high product quality, we need to have a way to keep the code simple, maintainable, decoupled and testable. Using an architectural pattern like MVP allows us to do this and to focus on the most valuable part of the code, our business logic.

MVP, or Model-View-Presenter, is one of multiple patterns that promotes separation of concern when implementing user interfaces, in which the roles of all these layers slightly differ. The goal of this article is not to describe the differences between these patterns, but to show how it can be applied to Android (as it is in most modern UI frameworks, such as Rails and iOS), and how it can benefit your app.

You can find sample code illustrating most of the techniques described in this post here: https://github.com/remind101/android-arch-sample

Old-school Android

The separation of concern as intended in the Android framework is the following: the Model can be any POJO, the View is your XML layout, and the Fragment (or Activity, originally) acts as the Controller/Presenter. In theory it works pretty well, but as soon as your app gets complex, you end up having a lot of View code in your Controller. This is because there is not much you can do in XML, so all the data-binding, animations, input detection, etc. ends up in the Fragment, along with your business-logic.

It gets worse since those complex different bits of UI can also be placed in Lists or Grids. The Adapter is now responsible for not only containing the View and Controller code for all those different parts, but also for managing them as a collection. As those parts are highly coupled, they become very hard to maintain and even harder to test.

Enters Model-View-Presenter

MVP offers us a way to dissociate all that tedious low-level Android code that is needed to display your UI and interact with it, which will live in the View, and the higher level business-logic of what your app should do, which live in the Presenter.

The way to achieve that on Android is to consider the Activity or Fragment as the View layer, and to introduce a lightweight Presenter to control it. The most critical part is to establish the responsibility of each layer and standardize the interface between them. Here is a high level separation of roles that has been working fairly well for us:

The View (Activity or Fragment) is responsible for:

  • Instantiating the Presenter, and its binding/unbinding mechanism
  • Informing the Presenter of relevant lifecycle events
  • Informing the Presenter of input events
  • Laying out the views and binding data to them
  • Animations
  • Event tracking
  • Navigating to other screens

The Presenter is responsible for:

  • Loading models
  • Keeping a reference to the model, and the state of the view
  • Formatting what should be displayed, and instructing the View to display it
  • Interacting with the repositories (database, network, etc.)
  • Deciding the actions to take when input events are received

Here is an example of what the interface between the View and Presenter could be:

interface MessageView {
 // View methods should be directives, as the View is just executing orders from the 
// Presenter.
 
 // Methods for updating the view
 void setMessageBody(String body);
 void setAuthorName(String name);
 void showTranslationButton(boolean shouldShow);
 
 // Navigation methods
 void goToUserProfile(User user);
}

interface MessagePresenter {
 // Presenter methods should mostly be callbacks, as the View is reporting events for the
// Presenter to evaluate
 
 // Lifecycle events methods
 void onStart();
 
 // Input events methods
 void onAuthorClicked();
 void onThreeFingersSwipe();
}

Here are a couple of interesting points to notice about this interface:

  • The methods to update the view should be simple and targeted on a single element. This is better than having a single setMessage(Message message) method that will update everything, because formatting what should be displayed should be the responsibility of the Presenter. For example, you might want to start displaying “You” instead of the user name if the current user is the author of the message, and this is part of your business logic.
  • Lifecycle events methods on the Presenter are simple and don’t have to map the (overly complicated) Android lifecycle ones. You don’t have to implement any of them, but you can implement as many as you want if the Presenter needs to take specific actions.
  • Input events methods on the Presenter are kept at a high level. If for example you’re trying to detect a complex gesture, like the 3 fingers swipe example, all the gesture detection should be done in the View.
  • You can see that there are MessagePresenter.onAuthorClicked() and MessageView.goToAuthorProfile() methods. The implementation of the View will probably have a click listener that calls this Presenter method that will call goToAuthorProfile(). Should you skip all this and just directly go to the author profile from the click listener? No! The decision of going to that profile when the author name is clicked is part of your business logic, and belongs in the Presenter.

As a rule of thumb, if your Presenter contains code from the Android framework and not pure Java, you are probably doing it wrong. And reciprocally, if your View needs a reference to the Model, you are probably doing it wrong too.

As far as tests are concerned, most of the code that you absolutely need to test will be in the Presenter. What’s great is that all this code doesn’t need Android to run, as it just has a reference to the View interface, and not its Android-specific implementation. This means that you can just mock the View interface, and write pure JUnit tests that make sure the right methods are called on that mock to test the integrity of your business logic. This is what your tests look like now.

What about Lists?

So far we made the assumption that our Views were Activities or Fragments, but they can be anything. What we have found to work pretty well for lists is to have the ViewHolder implement the View interface (either the RecyclerView.ViewHolder kind, or the plain old ViewHolder to use with a ListView). You just need some basic logic in your adapter for handling the binding/unbinding of the Presenters (there is implementation for all of this in the example repo).

If you consider the example of a screen containing a list of Messages, a loading spinner, and an empty View, the separation of concern now looks like:

  • The list Presenter is in charge of loading the Messages and the logic for showing list/empty/loading views
  • The Fragment is in charge of the implementation for showing the list/empty/loading view and navigating to other screens
  • The Adapter is in charge of mapping Message Presenters to their ViewHolder
  • The message Presenter is in charge of the business logic for a single Message
  • The message ViewHolder is in charge of displaying a single Message

All those components are loosely coupled and can easily be tested independently from each other.

Even better, if you have both a list screen and a detail screen for your Messages, you can re-use the same message Presenter and just have two different implementations of the View interface (the ViewHolder one and the Fragment one). This keeps your business logic DRY.

Similarly, the View interface can be implemented by custom Views. This lets you use MVP in custom widgets that can be reused throughout your app, or just allows you to break complex UIs into simpler blocks.

MVP and Configuration Change

If you have spent time writing Android apps you know how much of a pain supporting orientation and configuration changes is:

  • The Fragment/Activity must be able to re-create its state. Every time you work on a Fragment you must ask yourself how this would behave during orientation change, if there is something that needs to be persisted to the saved instance state Bundle, etc…
  • Long running operations in background threads are very hard to get right. One of the most common mistakes is keeping a reference to the Fragment/Activity in the long running operation (which is needed to update the UI when it finishes). This causes the old Activity to leak (and possibly crash the app due to increasing memory usage) and the new Activity to never receive the callback (and never update its UI).

Using MVP properly can actually take care of this for you without even having to think about it. Because the Presenter objects have no strong references to the actual UI they are very lightweight and can be retained during orientation changes! Because the Presenter keeps a reference to the Model and the state of the View, it is able to re-create the View in the correct state after the orientation change. Here is an approximative description of what happens during orientation change with this pattern:

  • Activity is initially created (let’s call this instance one) - New Presenter is created
    • Presenter is bound to the Activity
  • User clicks on the download button
    • Long running operation starts in the Presenter
  • Orientation changes
    • Presenter is unbound from the first instance of the Activity
    • First instance of the Activity has no reference, is available for garbage collection
    • Presenter is retained, long running operation continues
    • Second instance of the Activity is created
    • Second instance of the Activity is bound to the same Presenter
  • Download finishes
    • Presenter updates its view (the second instance of the Activity) accordingly

You can see an implementation of how to retain Presenters across configuration changes in the example repo in the PresenterManager class.

Wrap Up

That’s it! Hopefully this shows you how using a separation of concern pattern like MVP can help you write maintainable and testable code.

To summarize:

  • Separate your app business logic by moving it to a plain java Presenter object
  • Spend time writing a clean interface between your Presenter and your View
  • Have Fragment, Activities or custom views implement the View interface
  • For lists, have the ViewHolder implement the View interface
  • Test your Presenter thoroughly
  • Retain your Presenters during orientation change

You can find an implementation of all this in the ArchExample repo

There are also plenty of libraries out there that can help you use such patterns and do better separation of concern in your app, like Mosby, Flow and Mortar or Nucleus, that I encourage you to take a look at.