Transitive Closure in PostgreSQL
At Remind we operate one of the largest communication tools for education in the United States and Canada. We have...
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
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.
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:
The Presenter is responsible for:
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:
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.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.
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:
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.
If you have spent time writing Android apps you know how much of a pain supporting orientation and configuration changes is:
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:
You can see an implementation of how to retain Presenters across configuration changes in the example repo in the PresenterManager
class.
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:
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.