Cooking up clean architecture on Android (talk writeup)
I just got back from the lovely mediteranean shores of Tel Aviv, where I gave this talk. The slides, as well as a writeup of the talk itself are below. But first, let’s mention the event itself.
Codemotion - the venue, the setup
Codemotion is a developer conference (network?) that started in Italy, and then spread all over the place. The 2015 cities included Rome, Milan, Madrid, Berlin and Tel Aviv, with many more in the pipeline for 2016 - including London and Dublin. This was my first engagement at a Codemotion conference, but not the last one I hope. Also, they have one of the best talk submission processes. With a single account on their website you can submit talks to any of their conferences.
Also, many thanks and congratulations go to Ofir who made it happen. The organisation was great in all aspects. The venue, the setup, everything.
I mentioned the venue. It was a in a cinema multiplex, with the talks taking place in a theatre. That also showed Star Wars. As expected, it was an amazing experience seeing my slides on a biiiig screen.
Cooking up Clean Architecture on Android
Note: this writeup is based on my speaker notes, and roughly turned into a blog format. It follows the slides progression.
### The basics So, the architecture. It’s pretty important mkay? Why, you ask! There are several reasons.
First off, it allows you to isolate your components and decrease coupling. That’s great, because it means you can develop them on their own, and replace them when need be. Also, testing.
Then you have patterns. Once learned they allow you to recognise them elsewhere in other people’s code. It also makes communicating with other developers way easier.
And when we’re giving name to architectures, that’s actually giving names to patterns. We will be focusing on MVP, or Model-View-Presenter pattern. This is an important bit of the ‘Clean’ architecture, as defined by Uncle Bob. I see it as a logical extension of thinking from the Clean Code principles.
Idea of Clean architecture is in the flow of dependencies. Business logic, as the core of the application, is untainted by UI or database.
Speaking of UI, this is what uncle Bob says about it: “A good architecture hangs the UI off the side like an appendix. That makes it easy to remove when it gets inflamed.”
Here’s where the fun starts.
The “Android” definition of “clean”
When you create a new project in Android Studio, the first thing it creates is an Activity. That’s part of the UI. We can’t work without an Activity because that’s where the Context is and Context is a God object you need for pretty much everything. Starting other activities, getting services, access to the file system… We can’t avoid it.
Enter MVP
Let’s say we’re making a shopping app, and looking at the scenario of adding items to the basket.
Our journey starts with the view, where the user clicks a button to update the basket. That calls a method on presenter, which relays it to the model layer that does the business logic. Model returns to the presenter with the updated basket, which in turn tells the view what to do.
If we do the splitting of layers right, only the View will contain any Android code. By that I mean, only view can have imports of type com.android.*
.
Of course, it’s not that simple.
Doing anything interesting, like accessing sensors or SharedPreferences either needs Context or depends on android framework. The way to deal with that is to wrap and inject these dependencies into the model.
In the next few paragraphs we’ll go through each layer and look at how we could implement it.
Model Layer
This is where the magic (i.e. business logic) happens. This will call the APIs, persist data to the database, handle location updates, etc…
Good for us is that most of this is pure Java, and the only thing we need to do is figure out how wrapping works. First we need to create an interface which has similar (or identical) methods as the original API. In our example we need a location update from the Play Service API, so our method in question is requestLocation(...)
.
The model sees only the interface, and the wrapper does the actual calls, keeping us free from the framework. Job done.
Presenter Layer
This fella does the communicating between the ui and the logic. I tend to construct them in a way where the view and presenter are not equal members of the conversation. The view can only inform the presenter what action has occured, and the presenter orders the view what to do.
It will need a reference to the view and the relevant parts of the model. As Android is build view-first, our view will call initialise()
method with itself.
Keep in mind that whenever you keep a reference to the view you keep a reference to the context. If that gets destroyed then memory leaks can occur. That’s why there is also the viewDestroyed()
method in our interface.
Then we have 2 ways of acccessing the model. According to the Clean architecture, we’ll need interactors. They are just functional interfaces with a single method to perform the specific action. That action then calls the relevant API and returns the results. They tend to run in their own thread to ensure computation or I/O don’t block the UI.
Another option, a bit less clean is to have presenter call model directly. In our case that is the basketAPI.updateItemQuantity()
call. In my opinion this approach needs less classes and looks simpler so it can work for smaller apps. The obvious tradeoff is that it is more tightly coupled and makes testing harder.
#### View Layer
The view (lowercase) can be either an Activity or a View (android widget). (Or a fragment if you must, but I won’t go there).
It does as little as possible, but still a lot. Apart from telling the presenter what happened, and initialising it, it is also responsible for showing items on screen, and navigation between activities. Most importantly, don’t let it contain any business logic. Or kittens die.
Keeping the com.android.*
in the view means it’s not possible to run classic jUnit tests on them. We’ll need the instrumentation framework for that, and do some UI testing. These tests take longer to set up, run on a device and are much slower.
Another interesting one is how to navigate between activities. That operation needs a context (twice, because reasons). What we can do is know where we can go from current part of the app (in our case that is CheckoutActivity) and have a method that will start that Activity.
Tools & Libraries
There’s a lot of libraries that make our development cleaner and nicer out there. I recommend checking them out:
- Retrofit is great for accessing REST APIs. It transforms your REST method calls (GET, POST, PUT) into java interfaces. Easy to use, easy to mock.
- Dagger is the main dependency injection framework people use, and for good reason. It allows us to decouple components and makes testing a breeze.
- MVP Libraries - they make the hard work for you, and provide conventions to avoid memory leaks and other nasties. Examples are Mosby and Nucleus.