Some might argue that it doesn’t really matter the design pattern you choose, as long as you have a good separation of concern. I used to think so as well, but I noticed that in practice, the implementation of MVP in Android yields some interesting results. So when implemented correctly (I will explain what I mean later), it can be a handy way to separate logic from UI. I have seen many existing libraries that enable you to do MVP in Android like Mortar or Nucleus and it seems like the pattern of choice when developing on Android. In this post I won’t go into details on what is MVP, because many did that before me and you can probably find countless resources on this pattern online. What I will write about is some anti-patterns that I have seen when applying those patterns on the Android platform.
Using Android Components as the View
Often times, developers just use the Fragment/Activity as the view type of the generic presenter (e.g. interface MyPresenter<MyFragment>). I think that this is a very problematic step, for the following reasons:
- Reducing testability: in order to unit test the presenter we need to mock the fragment or worse, create one. This usually involves Robolectric or instrumentation tests. I won’t repeat what I wrote in the previous blog post about the issues with Robolectric.
- Tight Coupling: there is a very strong coupling between the presenter and the fragment it references. If we ever want to change the implementation of the view to be an Activity or a custom View we cannot do that without changing the presenter. This violates the Open/Closed Principle and makes our design rigid.
- Implicit Interface: the Fragment will contain all the public methods that are part of his role as the view of this presenter as well as the fragment lifecycle methods and methods that are used by the Activity to send messages to the Fragment. If a developer wants to know what are the methods that are called from the presenter on the view, he/she needs to find all references to the fragment in the presenter and see which method it hits.
So my suggestion in this case is simple: create a MyView interface and implement it by MyFragment so that the presenter will be MyPresenter<MyView>. This removes all the issues listed above. There is a some boilerplate code involved in hooking the view to the presenter that you usually want to put a specific lifecycle events of the Fragment, but that can be achieved easily by creating a base fragment and base presenter.
The Case of the Sneaky MVVM
While working with a colleague, he told me that his Views have only one method which is setState(ViewState state); so essentially every time the state changes he invokes that method in the Fragment/Activity/Custom View and that view is in charge of doing a delta. You might think, what’s wrong with that? so a few things:
- The God Object: first of all, he has this God object called the ViewState that can be changed without any clear indication in the interface. Some developer can come later and add some behavior to that object and then you end up with essentially a mish mash between MVVM and MVP.
- Implicit Interface: again, the interface of the view is all about the state object, so I don’t know which behaviors does the view have by looking at this single method interface. It is hidden in the state object.
- Sneaky MVVM: why do I call this the sneaky MVVM? because if you move the methods of the presenter to the state object you end up with something very similar to MVVM. Why is that a problem? people know design patterns, design patterns are tested, long living ways to design your code and having this made up version in your code can cause you a lot of grief if you scale your team. In large scale mobile apps with big teams, if a developer adopts this hybrid pattern, other developers might abuse it (see first bulletin – The God Object) and create a mess of responsibilities. It is hard enough to make sure that developers follow the existing pattern, so adding this custom pattern is even harder to implement.
The Huge Presenter
When people implement MVP it is to avoid having huge Activities/Fragments, better separation of concerns as well as better testability. In big and complex applications, if not combined with other patterns (I am a fan of Clean Architecture myself, and here is an example of how to implement it on Android) you end up with massive presenters. This, by the way, can happen also with MVVP or any other MV* pattern. These patterns are for UI, but if your business logic is very complex and big you should make sure you have some more abstractions to minimize the presenter.
MVP is a good pattern when you don’t fall into it’s traps on Android. I personally would prefer MVVM since the data binding library enables you to write less glue code and you are less likely to fall into anti-patterns as listed above.