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.

Related

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.