Muselee is a demo app which allows the user to browse popular music artists. It is not intended to be a fully-featured user app, but a vehicle to explore good app architecture, how to implement current best-practice, and explore how the two often go hand in hand. Moreover it will be used to explore how implementing some specific patterns can help to keep our app both maintainable, and easy to extend.
Previously we looked at the UI components and explored how they worked within an MVVM pattern, but there is something more going on when we consider the entirety of the TopArtists
module – the feature module itself implements a Clean Architecture, and in this article we’ll explore precisely what that means, how it has been implemented, and what benefits it gives us.
I have deliberately implemented this as as simple an example of clean architecture as was possible – it consists of three layers. The fundamental nature of Clean Architecture is of concentric layers – like the layers in an onion, or the annular rings of a tree trunk. At the centre of this structure we have our entities which comprises our internal domain data model, and any core business logic. Immediately surrounding this we have a transformation layer, which marshals and transforms data to that core domain model. The outer most layer which surrounds that is our IO layer which is what interfaces with any external frameworks and services.
In the Entities
layer we have our core domain model which is the data model in entities/Artists.kt
and the data retrieval state represented by entities/TopArtistsState
. There is currently no business logic in here, because I’ve gone for simplicity for now, but we’ll add some later on.
In outer IO
layer we have the components which communicate with external frameworks and services. This is both our network components, and the code which integrates with the Android framework such as the View
components of our MVVM.
The middle Transformers
layer are the components which connect their respective components in the IO
layer to the components within the Entities
layer. This is view/TopArtistsViewModel
on the UI side.
One of the fundamental rules of Clean Architecture is that any layer can have dependencies on inner layers, but should not have any dependencies on those layers further out. So The Entities
layer can have no dependencies on any of the other layers; the Transformers
layer can have dependencies upon the items in the Entities
layer, but not the IO
layer; and the IO
layer can have dependencies on both other layers.
For this reason net/LastFmTopArtistsProvider
should really exist within the Transformers
layer, but I have removed a level of abstraction in order to keep this example simpler – and the fact that it still knows about the LastFmArtist
data class (which is an externally defined data model) means that it is actually part of the IO
layer.
The DataProvider
interface that we added to the Core
module is a good enabler for Clean Architecture and allows us to work around this. LastFmTopArtistsProvider
implements DataProvider<TopArtistsState>
and we can therefore have other components which have a dependency upon DataProvider<TopArtistsState>
rather than having direct dependencies on LastFmTopArtistsProvider
itself. That simple abstraction is really useful when it comes to adhering to the Clean Architecture dependency rule. Remember that TopArtistsState
is part of the IO
layer, so depending upon a generic interface of DataProvider<TopArtistsState>
instead of LastFmTopArtistsProvider
meets that criteria.
On the surface, it may appear that TopArtistsViewModel
depends on the View
components, but actually it doesn’t – thanks to the use of LiveData
. The TopArtistsFragment
obtains an instance of TopArtistsViewModel
and then subscribes to its LiveData
. The TopArtistsViewModel
itself merely updates the LiveData
and any subscribers will get notified of the change, so the TopArtistsViewModel
remains agnostic of the TopArtistsFragment
. Thus we have satisfied the dependency rule of Clean Architecture.
It is worth pointing out that although TopArtistsViewModel
appears to be part of the Android framework because it sub-classes ViewModel
from the Jetpack Architecture Components library. It actually ties in with the life-cycle of its parent Fragment
, but that is managed for us, and can be considered safe because it is both managed for us, and is actually done though interface abstractions which remove any direct dependency on the Fragment
itself, so does not break the Clean Architecture dependency rule.
This Clean Architecture actually provides us with a few benefits. Firstly, it means that anything in either the Entities
or Transformers
layer should not be touching APIs in the Android Framework itself, or any external services. This means that they should be easily unit testable because they are pure Java / Kotlin implementations. ViewModel
has been designed to be independently testable so meets this criteria. This does not mean that we cannot also make our IO
layer components unit testable because we can certainly create unit tests for most of the network components. But by keeping anything which touches the Android framework directly in the IO
layer, it means that we only need to consider slower, more expensive Espresso tests for the functionality that is carefully restricted to that layer.
A second benefit is that it enforces good separation of concerns which means that we can make changes in certain sections of the app without affecting others. For example, we could completely change our choice of data provider from last.fm to something else, and only have to worry about changing a relatively small, self-contained section of the code-base.
A third benefit is that we can change internal behaviours and business logic purely within the Entities
layer. We’ll explore this further later on in the series.
Of course there will always be cases where we need to make changes throughout all of the layers. Perhaps we want to also include the number of listeners for each artist (which is provided by the last.fm API). While we’re already receiving this from the network response, we would need to add a field to the Artist
domain model, populate this from LastFmTopArtistsProvider
and then add the necessary UI components to display it. However, this would require end-to-end changes whether or not we implementing Clean Architecture. That said, the Clean approach still offers benefit here, because by adding a field to the Artist
data class, we expose it to the UI
components automatically, and it enforces a responsibility of the network components (specifically LastFmTopArtistsProvider
) to populate this field.
It can sometimes be tricky to appreciate where we have dependencies, but there is a very simple test that we can do: Look at our imports! If we consider anything within the com.stylingandroid.muselee.topartists.view
package, the imports of none of those classes contain anything from the com.stylingandroid.muselee.topartists.net
package. Moreover, the dependency upon TopArtistsState
is limited to TopArtistsViewModel
. As I mentioned earlier, I have elected for simplicity here to keep things understandable, but in even slightly more complex scenarios it would be advantageous to limit the dependency on the Artist
domain model to this class as well. Once again, this is a concept that we shall explore further later in the series.
Clean Architecture is discipline in how we organise our components and connect them together. A good implementation of it will result in a code-base which is much easier to both maintain and extend.
In the next article we’ll turn our attention back to UI and see how we can present the data to the user in a more interesting way.
We haven’t actually added any new code in this article, but merely analysed the code that is already available here.
© 2019, Mark Allison. All rights reserved.
Copyright © 2019 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.
Mark, I just wanted to say that this is the best article series I’ve ever read on Android Architecture. It’s simple enough to follow, very well explained including the why’s and how’s. Thanks for taking it one step at a time, it makes it incredibly easy to follow the thought process. I’m hoping there’s a future lesson or two on adding tests, maybe using the Robot Pattern too? Anyways I’m eagerly looking forward to the upcoming adding “Business Logic” post that you mentioned above. Thanks so much!