Disclaimer: this is not a blog post about how to write your Android code in an MVP manner (God knows there are plenty of those already). Instead, these are just our personal experiences on how switching our presentation layer to an MVP architecture helped us tackle some accumulated technical debt, and in the process also helped us convert our app from a prototype to a more maintainable product.

Anyone who has worked with Android long enough and on large enough projects, has most likely reached a point where they’ve looked at their codebase and thought there must be a better way. We’re no different here at Picnic, and we reached that moment roughly eight months after development of the Android app started, and right around the time we released our first version to the public.

It is no surprise this moment coincided with the launch of our app. Up until that time, we were running at a very fast pace, constantly hammering away at our keyboards, building an entire product from scratch, trying out new things, incorporating user-feedback back into the app, adding and discarding features on a daily basis.

To keep up with the company’s speed we cut some corners here and there. This worked out great for us, and it was one of the reasons we were able to build and ship the app in such a short time. But as expected, eventually the effects of those decisions started to manifest themselves in the form of technical debt. Luckily, this technical debt built up over the months, never had any real impact on the app’s performance or stability. Instead, we began noticing it in other areas:

The turnaround time of new features increased.

Onboarding of new developers got harder.

It proved difficult to implement automated tests.

The overall complexity of features increased.

We already had a pretty well thought-out and easy to understand architecture when it came to the network layer, error handling, and inter-module communication inside the app. But, like most Android developers, we were guilty of pushing too much logic into our Activities and Fragments.

Sidenote: this is a common issue in Android development, and as developers we’re left a bit in the dark, since Google’s always been very silent about this topic. The first (kinda) official response we’ve gotten from them, is from a Google+ post by one of the developers on the Android team, stating that we should treat the core Android API as a ‘systems framework’, meaning they will hold our hands all the way up to the core Android components (Activity,BroadcastReceiver, Service and ContentProvider). What we do after that, is up to us. And only very recently, has Google finally provided a series of samples on how to tackle common issues regarding architecting an Android app, with a heavy focus on MVP. Although still in beta, it can be viewed here: Android Architecture Blueprints

Anyway, this is actually a good thing, as it means we are free to experiment with whatever approach we like instead of being forced by the platform to follow one specific pattern.

Going back to our story now… unless you lived under a rock in the Android dev world, you might have noticed that presentation-layer architectures are a hot topic right now. Everyone and their mother seems to have an opinion on what the best approach is. From doing things the standard Android way (MVC-ish), to MVP, to MVVM via data-binding, all the way up to Uncle Bob’s clean architecture. There are some interesting discussions around the pros and cons of each of these approaches, but one thing we knew for sure, was that we should avoid drinking the Kool-Aid and expect any one of these to be the silver-bullet that would forever solve all of our issues.

When considering how to re-structure our presentation layer, we already had almost a year’s worth of knowledge of the codebase, a very clear view of where we were falling short, and the goals we wanted to achieve with a new implementation (mainly, something that could tackle our technical debt points expressed above). We played around a bit with dummy projects, experimenting with the different approaches, and eventually decided to go with MVP. Since at its core, MVP itself is nothing more than a concept, and the Android framework, by design, does not enforce any patterns, we were free to pick the details of the actual implementation.

In the Android team, we’re true believers in not over-engineering things at first, and letting the code naturally evolve to where it needs to be over-time, instead of prematurely adding abstractions on top of abstractions in an attempt to prepare ourselves for the unpredictable future. For this reason, we’ve chosen a flavor of MVP that would keep our abstraction levels to the bare minimum. At the code level, this means having a single interface to represent the View. All other components are concrete classes. You might be asking yourself, how come only the View gets an interface? Given our immediate needs, this was the only component that would actually benefit from such an interface, since we actually do have different concrete views that share the same interface. So, in our case, an interface here would allow us to reuse the Presenters. A few MVP implementations online propose setting up interfaces for all components (the M, the V and the P). Even though this works perfectly fine, we’d advocate against it at this early stage, since the added cost to the code’s readability and maintainability, especially when considering onboarding junior developers unfamiliar with MVP, outweighs the benefits of this interface-based programming approach.

Other than that, the MVP implementation is pretty standard. The View (Activity, Fragment or a custom view) is in charge of creating and maintaining the Presenter, while this one handles any business-related logic (data fetching, storage, formatting, etc.), and reports back to the View by updating its UI when required. In our case, the Model layer was already quite modular, made up of POJOs which represented our data models, and a pre-existing control layer which handled network communication.

This is a pretty standard MVP setup, and because of its simplicity, we were able to convert almost all of our UI code in only a couple of weeks’ time. Since we already had the existing, self-contained Model layer to handle all API interactions with our backend, the only real refactoring needed was the interaction between the Views and the Presenters.

In the process of this refactoring, we also learned a few things that might come in handy:

Lifecycle : Since the Presenter is created by the View, we need to make sure it’s completely made aware of the View’s lifecycle, especially since it will most likely be dealing with state updates and asynchronous data. For example, each Presenter should have a way to cancel async tasks in case the view is destroyed, or should be able to reset itself to the original state in the event of a user pausing and resuming the view, etc. Last but not least, always watch out for the dreaded NPEs when trying to update view elements from the Presenter when the view has already been destroyed.

: Since the Presenter is created by the View, we need to make sure it’s completely made aware of the View’s lifecycle, especially since it will most likely be dealing with state updates and asynchronous data. For example, each Presenter should have a way to cancel async tasks in case the view is destroyed, or should be able to reset itself to the original state in the event of a user pausing and resuming the view, etc. Last but not least, always watch out for the dreaded NPEs when trying to update view elements from the Presenter when the view has already been destroyed. Keep views as dumb as possible : Our views should no longer contain any business-related logic. It should really just be the bare minimum required by the Android framework to inflate and set up the view. Any user-interaction should be forwarded to the Presenter. As a rule of thumb, if your views have anything other than methods to either update their UI elements or react to user-triggered events, then you should probably review their implementation.

: Our views should no longer contain any business-related logic. It should really just be the bare minimum required by the Android framework to inflate and set up the view. Any user-interaction should be forwarded to the Presenter. As a rule of thumb, if your views have anything other than methods to either update their UI elements or react to user-triggered events, then you should probably review their implementation. Keep the Presenters as pure as possible: By this we mean that you should do your best to avoid having Android-specific code inside your presenters. This significantly simplifies writing pure unit tests for these components, without the need to use other testing frameworks such as Robolectric. This is easier said than done of course, since you will eventually run into situations where, for example, you will need to have a reference to the Context for things like data loading, access to the strings file, etc.

Conclusion

So, after all was said and done, what was the final outcome? Overall, we’re pretty pleased with how MVP turned out. It definitely helped us tackle the technical debt we had accumulated as a result of moving fast, and we are now much more prepared for this second phase of development.

A couple of positive takeaways worth mentioning:

Test count : Before the refactoring the number of tests we had could be counted on two hands. It was a monumental task to write tests around Activities that contained all logic to perform data parsing, formatting, network calls, error handling, and the management of their own lifecycle. Just thinking of how to write a test under these conditions was enough to make us look the other way. As soon as we converted our first piece of code into MVP, writing tests around it became trivial. With a clear contract of what a view is able to handle, we were able to distance ourselves from the intricacies of the Android UI framework, and just test that the correct methods were actually being called, given each test scenario. The actual business-related logic now lives inside the Presenters, and since most of them have no knowledge of the Android OS (or the little knowledge they do have can be mocked), we’re able to write very efficient unit tests around them as well. As a result, we increased our test cases from around 10 to over 900, and growing, in just the past few months.

: Before the refactoring the number of tests we had could be counted on two hands. It was a monumental task to write tests around Activities that contained all logic to perform data parsing, formatting, network calls, error handling, and the management of their own lifecycle. Just thinking of how to write a test under these conditions was enough to make us look the other way. As soon as we converted our first piece of code into MVP, writing tests around it became trivial. With a clear contract of what a view is able to handle, we were able to distance ourselves from the intricacies of the Android UI framework, and just test that the correct methods were actually being called, given each test scenario. The actual business-related logic now lives inside the Presenters, and since most of them have no knowledge of the Android OS (or the little knowledge they do have can be mocked), we’re able to write very efficient unit tests around them as well. As a result, we increased our test cases from around 10 to over 900, and growing, in just the past few months. Predictability: This is a bit of a soft metric, but a very powerful one. By picking and sticking with a common pattern for our UI, we get the added benefit of predictability in the codebase. This means, no matter what kind of UI element a developer looks at (Activity, Dialog, Fragment, etc), if they understand how one works, they understand how all of them work. There’s no longer an element of surprise when opening a file you’ve not written yourself. The responsibilities are clearly laid out, and every single UI component follows the same exact pattern. This is invaluable when onboarding new developers and getting them to be productive from day one.

Let’s not forget MVP is only for the presentation layer, but as front-end developers this where we spend most of our time. So it was worth the effort to find a solution that gives us more predictability and lets us iterate faster as new developers join us. All things considered, we can safely say MVP was a major milestone in helping us achieve this.

P.S. If you’re still dying to see some source code, here’s a stripped-down version of our MVP implementation for the ‘Forgot Password’ use case, displaying the interaction between the MVP components as the user clicks the “Reset Password” button once they’ve entered their email address (Android boiler-plate code has been left out to keep the code concise):