CLEAN-up your Activities (Part I): Decoupling from Android Framework datasources

By jesse orrico on Unsplash

This is the first part of a series of articles that explores ways to keep the Activities in an Android app “clean”. And we are using here the word “clean” both in the sense of readable, maintainable and concise code, as well as in the sense of applying correctly the principles of “clean architecture”.


Before going deep into this, let’s first review how clean architecture (with MVP in the presentation layer) is typically implemented in the case of an app that fetches some data from a remote server and displays them on screen.

  • The View notifies the Presenter that data needs to be loaded (either in its initialization phase, or based on user input).
  • The Presenter forwards the request to the Interactor.
  • The Interactor processes and forwards the request to the Network Client.
  • The Network Client (which is typically implemented with Retrofit) issues the request to the Server.
  • The Server sends back the json response, which is received by the Network Client.
  • The Network Client parses the request, maps the network model to the domain model and returns that to the Interactor.
  • The Interactor might implement some additional operations upon the reception of the response, and then returns the domain model to the Presenter.
  • The Presenter maps the domain model to the presentation model, and sends instruction to the View on how to display it.
  • The View reacts to the Presenter’s instructions and displays the data on the screen.

The above is an implementation of a layered architecture (View and Presenter in the Presentation layer, Interactor in the Business layer and Network Client in the Data layer), with clear separation of responsibilities and a nice flow from the View to the Server and back to the View. However, this kind of architecture might seem like an overkill in simple cases, like in the example of a single network request that we’ve used: the Interactor is hardly doing any work other than passing around request and response data. 
But in real-life situations, the Interactor would probably do more things. Maybe for a single user interaction, more than one network requests are required, and the Interactor will be the one that launches the various requests and composes their responses into a single result. Or it might be the case that an operation has some side-effects, and the Interactor will be responsible for performing them.

So, the reasoning behind this kind of architecture is stronger in non-trivial apps. However, the more complex things get, the harder it is to remain focused and committed to the architecture’s objectives and principles.

In Android, this often happens when we start interacting with the Android framework as source of data for our app. Accessing some features, like location services, sensor data, content providers and permissions (to name a few), requires using an instance of Context or an Activity. What we normally end up doing, is just include all relevant code in the Activity itself. This not only contributes in turning the Activity into a God object, but it also has a detrimental effect in the architecture of the app:

  • The MVP View ceases to be dumb. It becomes responsible for accessing core data providers for the app, and probably includes a non-trivial amount of presentation (or even business) logic in order to handle communication with these providers and process the results before displaying them on the screen.
  • It also violates one very important principle of “clean architecture”, that of independence from frameworks. We end up depending directly on the Android framework, rather than depending on an abstraction, as clean architecture dictates. Furthermore there is no separation of concerns or clear boundaries between the layers of the application.

In continuation, we will use a detailed example of an app that uses the Location Services to demonstrate exactly how the naive approach of “throwing everything in the Activity” violates architecture principles, and then we’ll see how we can refactor and significantly improve the code.


Our app contains an Activity that simply displays the latitude and longitude of the user’s last known location. When the Activity is first launched, the FINE_LOCATION permission is requested.

  • If it’s granted, everything continues normally, we request the location, and when it’s available we show the coordinates.
  • If the user denies the permission request, a message is shown, that informs the user that location permissions are required. If the user clicks on that message, the system permission request dialog is shown again.
  • If the user clicks on “Do not ask again”, a different error message is displayed to the user. If this error message is clicked, the user is taken to the app’s system settings, where they can grant the permission if they wish.

Here are some gifs that demonstrate the app’s functionality:

Permission is granted
Permission is soft-denied
Permission is hard-denied (“Do not ask again”)

The full source code of the app is available in the following repository:

This repository contains various branches that follow the narrative of this series of articles: we will start with what would be the naive approach (branch naive_mvp) and walk up from there to gradually refactor the code. All code snippets below are labelled with the branch they were taken from.


The naive approach

In the naive approach, we would start with creating an empty Activity, then we would create an empty MVP contract, an empty implementation for the Presenter and we would wire up everything with dagger. We would then proceed with creating the layout for the Activity and finally we would reach the point where we would have to start implementing the presentation and application logic.

Since accessing Location Services to create a FusedLocationProviderClient requires passing an Activity, we would “naturally” place the relevant code in our Activity: We would create a FusedLocationProviderClient in the Activity and we would use it to request the user’s last location. As soon as a Location was returned, we would pass the coordinates to the Presenter, which would then instruct back the Activity on how to display the data.

But before we got to actually use the FusedLocationProviderClient, we would need to check for permissions and if necessary, request the runtime permissions from the user. Requesting permissions also requires passing around the Activity object, and handling the result is done by overriding Activity.onRequestPermissionsResult, so we would also add all this code in the Activity.

Our Activity now would look like this:

From naive_mvp branch

We will ignore for the moment the handling of the permissions, which will be the main topic of Part 2 of this series. But even ignoring this, we can see that our MVP View (the Activity) does a lot more than just dealing with presentation. Accessing the location data source (in this case Google’s Location Services) is part of the business logic of the app and should not take place in the presentation layer.

Let’s have a look at the Presenter, which is limited in receiving the location coordinates and passing them back to Activity:

From naive_mvp branch

It’s tempting to think that this code is pointless and we don’t need to use the Presenter at all: since we are accessing the LocationServices inside the Activity and receiving the Location there, we can just set the TextViews’ text inside the OnSuccessListener's callback, without passing through from the Presenter. The mere fact we are tempted to think like this is an indication that there’s something wrong with the code.

Let’s stick to the MVP principles however, that state that all data has to go through the Presenter before being displayed on screen and that it’s the Presenter’s responsibility to decide how this data will be presented. In our case the Presenter just transforms the doubles to Strings, but it wouldn’t be unreasonable to imagine that there could be some additional formatting logic in the Presenter.

This justifies the necessity for the Presenter, but even on these premises, the View-Presenter dialog is not very natural.

(View) - Hey, Presenter, here’s the location coordinates.
(Presenter) - Oh, thanks for giving me the coordinates. Now, please take them back and paint them…

This awkward interaction differs a lot from the nice flow we observed in the network request case. The flow in that case went from the View, through the presenter to the domain layer, up to the source of the data, and then back again to the View. Wouldn’t it be nice if we could achieve a similar flow in the case of location data? Let’s see how we can refactor the code in order to accomplish this.

Decoupling from the Location Services

All the problems that we’ve identified in the naive approach originate from the fact that we have coupled our Activity to the Location Services. The Location Services from the Android Framework, and more specifically the FusedLocationProviderClient that we are using, is, under Clean Architecture terminology, a “detail”. As such, our code should not depend on it; rather this detail should depend on an abstraction (by implementing it actually) and our code should simply depend on the framework-independent abstraction.

The solution is therefore to simply extract an interface that abstracts away what the Fused Location Provider is expected to do for our app: provide the user’s location.

From decouple_framework branch

This interface belongs to the domain layer of our app and is independent from the Android framework. Note that the Location object that this method emits is a custom domain object (not android.location.Location).

From decouple_framework branch

We can now create an Android-specific implementation of the LocationProvider interface, that uses the Android Framework’s Location Services and is, essentially, an rx-java wrapper around the FusedLocationServiceClient.

From decouple_framework branch

The implementation requires an instance of a Context in order to access the LocationServices. The Context is provided in the constructor, through dependency injection, with the help of dagger-android (and it should preferably be the ApplicationContext, rather than an Activity). Check the full source code in the branch decouple_framework to see how all this is set up.

Now we can create an Interactor that uses a LocationProvider to get a location and then passes it back to the presenter. The Interactor will depend on the abstract interface LocationProvider, but the instance that will have injected will be anAndroidLocationProvider.

From decouple_framework branch

The Presenter now looks like this:

From decouple_framework branch

Note that the Presenter interface in the Contract has changed: we have removed the dubious method onLocationAvailable (that the View used to call in the naive case to forward the location coordinates to the Presenter) and we have added the getLocation method: the View calls this method whenever it wants to request a location; the Presenter is then responsible for requesting the location from the domain layer, and when it receives it, it pushes it back to the View.

Here’s how the Activity looks like now:

From decouple_framework branch

There is still a lot of boilerplate related to the permissions, which makes it a bit hard to appreciate the benefits that the refactoring has brought to the of Activity, but we can still appreciate that it doesn’t contains any code related to the request of the location; it only responds to the Presenter’s instructions, just like a well-behaved dumb view is expected to do.

Furthermore, the flow now is very similar to the one we saw in the beginning of the article regarding the network request: the diagram below is effectively the same, with the only difference that we have replaced the Network Client with the LocationProvider, and the remote server with Android’s Location Services.

Here’s a summary of the benefits that our refactoring efforts have brought to the code so far.

  • Clear separation of the layers. In the naive approach we didn’t really have layers, we just had a God Activity, and we even doubted about the necessity of using MVP.
  • Separation of concerns - the View is not responsible for executing business logic (fetching the location), but only for displaying the data when it’s available. The layer which is responsible for fetching the location is the domain layer (Interactor), as it should be.
  • The data flow for accessing the location follows the same path as with accessing network or database data. This is particularly important for handling the flow when we have multiple data sources, because then we can compose everything into one flow that starts from the View, goes into the business logic layer and then returns to the View. In our example, if we needed to send the location over to a server, the Interactor could do that in one shot when it received the location from the LocationProvider, and before sending it back to the View.
  • Our code is now more testable, as it is decoupled from the Location Services. We can create, for example, a mock LocationProvider and use it in Espresso tests; this would be very hard to do in the naive implementation.

By decoupling the LocationActivity from the Location Services, we have gained significant improvements in code quality, but still, the code leaves much to be desired. In the second part we will clean up the Activity even further, by decoupling it from the permissions request process.