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.
Entities layer we have our core domain model which is the data model in
entities/Artists.ktand 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.
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.
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
DataProvider interface that we added to the
Core module is a good enabler for Clean Architecture and allows us to work around this.
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
TopArtistsFragment obtains an instance of
TopArtistsViewModel and then subscribes to its
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
Fragmentitself, 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
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.