Let's learn, share & inspire each other. Sign up now 🤘🏼
Model-View-Presenter: Android guidelines
In this article, I’m going to put down a list of guidelines or best practices to follow in order to:
First of all, let’s describe the players:
Model-View-Presenter
The following are guidelines from my personal point of view and you might not like them, then I will try to explain why these principles seem reasonable to me.
1. Make View dumb and passive
One of the biggest problems of Android is that views (Activities, Fragments,…) aren’t easy to test because of the framework complexity. To solve this problem, you should implement the Passive View pattern. The implementation of this pattern reduces the behavior of the view to the absolute minimum by using a controller, in our case, the presenter. This choice dramatically improves testability.
For example, if you have a username/password form and a “submit” button, you don’t write the validation logic inside the view but inside the presenter. Your view should just collect the username and password and send them to the presenter.
2. Make presenter framework-independent
In order to make the previous principle really effective (improving testability), make sure that presenter doesn’t depend on Android classes. Write the presenter using just Java dependencies for two reasons: firstly you are abstracting presenter from implementation details (Android framework) and consequently, you can write non-instrumented tests for the presenter (even without Robolectric), running tests faster on your local JVM and without an emulator.
Well, get rid of it. In cases like this, you should ask yourself why you need the context. You may need the context to access shared preferences or resources, for example. But you shouldn’t do that in the presenter: you should access to resources in the view and to preferences in the model. These are just two simple examples, but I can bet that the most of the times it is just a problem of wrong responsibilities.
By the way, the dependency inversion principle helps a lot in cases like this, when you need to decouple an object.
3. Write a contract to describe the interaction between View and Presenter
When you are going to write a new feature, it is a good practice to write a contract at first step. The contract describes the communication between view and presenter, it helps you to design in a cleaner way the interaction.
Just reading the method names, you should be able to understand the use case I’m describing with this contract.
The View contract
Like I said before, the view is implemented by an Activity (or a Fragment). The presenter must depend on the View interface and not directly on the Activity: in this way, you decouple the presenter from the view implementation (and then from the Android platform) respecting the D of the SOLID principles: “Depend upon Abstractions. Do not depend upon concretions”.
We can replace the concrete view without changing a line of code of the presenter. Furthermore, we can easily unit-test presenter by creating a mock view.
There are two different schools of thought about this topic.
Some people think you should write the Presenter interface because you are decoupling the concrete view from the concrete presenter.
However, some developers think you are abstracting something that is already an abstraction (of the view) and you don’t need to write an interface. Moreover, you will likely never write an alternative presenter, then it would be a waste of time and lines of code.
Personally, I prefer to write the Presenter interface for two simple reasons (besides those I listed before):
It’s been a year, more or less, that I write Contracts like this and I’ve never seen this as a problem.
4. Define a naming convention to separate responsibilities
The presenter may generally have two categories of methods:
load()
for example): they describes what the presenter doesqueryChanged(...)
for example): they are actions triggered by the user like “typing in a search view” or “clicking on a list item”.More actions you have, more logic will be inside the view. User events, instead, suggest that they leave to the presenter the decision of what to do. For instance, a search can be launched only when at least a fixed number of characters are typed by the user. In this case, the view just calls the queryChanged(...)
method and the presenter will decide when to launch a new search applying this logic.
loadMore()
method, instead, is called when a user scrolls to the end of the list, then presenter loads another page of results. This choice means that when a user scrolls to the end, view knows that a new page has to be loaded. To “reverse” this logic, I could have named the method onScrolledToEnd()
letting concrete presenter decide what to do.
What I’m saying is that during the “contract design” phase, you must decide for each user event, what is the corresponding action and who the logic should belong to.
5. Do not create Activity-lifecycle-style callbacks in the Presenter interface
With this title I mean the presenter shouldn’t have methods like onCreate(...)
, onStart()
, onResume()
and their dual methods for several reasons:
presenter.onCreate(state)
method? In fragment’s onCreate(...)
, onCreateView(...)
or onViewCreated(...)
? What if I’m using a custom view?Instead of calling a method of the same name, in an Activity lifecycle callback, you can call a presenter’s action. For example, you could call load()
at the end of Activity.onCreate(...)
.
6. Presenter has a 1-to-1 relation with the view
The presenter doesn’t make sense without a view. It comes with the view and goes when the view is destroyed. It manages one view at a time.
You can handle the view dependency in the presenter in multiple ways. One solution is to provide some methods like attach(View view)
and detach()
in the presenter interface, like the example shown before. The problem of this implementation is that the view is nullable, then you have to add a null-check every time presenter needs it. This could be a bit boring…
I said that there is a 1-to-1 relation between view and presenter. We can take advantage of this. The concrete presenter, indeed, can take the view instance as a constructor parameter. By the way, you may need anyway a method to subscribe presenter to some events. So, I recommend to define a method start()
(or something similar) to run presenter’s business.
If you have a method start()
, you may need at least a method to release dependencies. Since we called the method that lets presenter subscribe some events start()
, I’d call this one stop()
.
7. Do not save the state inside the presenter
I mean using a Bundle
. You can’t do this if you want to respect the point 2. You can’t serialize data into a Bundle
because presenter would be coupled with an Android class.
I’m not saying that the presenter should be stateless because I’d be lying. In the use case I described before, for instance, the presenter should at least have the page number/offset somewhere.
8. No. Do not retain the presenter
I don’t like this solution mainly because I think that presenter is not something we should persist, it is not a data class, to be clear.
Some proposals provide a way to retain presenter during configuration changes using retained fragments or Loaders. Apart from personal considerations, I don’t think that this is the best solution. With this trick, the presenter survives to orientation changes, but when Android kills the process and destroys the Activity, the latter will be recreated together with a new presenter. For this reason, this solution solves only half of the problem.
9. Provide a cache for the Model to restore the View state
In my opinion, solving the “restore state” problem requires adapting a bit the app architecture. A great solution in line with this thoughts was proposed in this article. Basically, the author suggests caching network results using an interface like a Repository or anything with the aim to manage data, scoped to the application and not to the Activity (so that it can survive to orientation changes).
This interface is just a smarter Model. The latter should provide at least a disk-cache strategy and possibly an in-memory cache. Therefore, even if the process is destroyed, the presenter can restore the view state using the disk cache.
The view should concern only about any necessary request parameters to restore the state. For instance, in our example, we just need to save the query.
Now, you have two choices:
repository.get(params)
, if the page is already in cache, the data source just returns it, otherwise the APIs are calledrestore(params)
, loadFromCache(params)
or reload(params)
are different names that describe the same action, you choose.Conclusions
I think we are done. That’s it. This is my knowledge about Model-View-Presenter applied to Android. I tried to learn from my mistakes, others’ and my experiences. I’ve been constantly finding the best approach.
I hope you liked this article. If you have any feedback, feel free to comment below: I would be very happy to get suggestions and to see any different solution.