At Moove-it we work hard to create beautiful, scalable and maintainable Android apps. To achieve that goal, over the years we have tried different architecture approaches (MVP, MVVM, MVC and some custom architectures) until we finally encountered the Clean Architecture.
In this post we introduce the Clean Architecture and then explain the approach we took to use it on Android. If you are a show-me-the-code person, you can jump directly to our example on GitHub.
About the Clean Architecture
The Clean Architecture was introduced by Uncle Bob. Its main goal is the separation of concerns by diving software into layers (see image). What makes it work is the dependency rule, which basically states that source code dependencies can only point inwards. So the idea is that inner circles are not aware of the outer circles and only the outer circles know that the inner layers exist.
Following this rule, we can build software holding the following quality properties:
- Independency of Frameworks.
- Independency of the UI
- Independency of the Database
- Independency of any external agency
We decided to separate our logic into three layers:
- Domain: This is the layer where all the business logic occurs, we do not have any Android dependency here.
- Data: It is in charge of getting the data for the app.
- Presentation: Everything related with the UI logic and the render.
We use MVP to develop our presentation layer.
The view is a set of interfaces that could be implemented by any Android view, such as Activities, Fragments or Custom Views. These views are dummy in the sense that they only act in response to orders from the presentation layer.
Serve as a middleman between views (abstractions over Android specific components) and the business logic (interactors).
Here we introduce the interactors. Each interactor is a reusable component that executes a specific business logic. In the GitHub example, the use cases are: “Add a pet”, “Edit a pet”, “Delete a pet”. It fetches the data from a repository, executes the business logic and returns the result to the presenter.
We use the Repository Pattern to create an abstraction of the datasources from which the interactors get the data to act upon. This example uses a database as a local datasource and cache mechanism and also a remote datasource that syncs with the server using a REST API. Initially, the repository persists the changes locally and then it syncs with the server. It uses the local data unless it’s told that the cached data is stale, thus needing to sync with the server. The syncing mechanism is pretty simple: wipe the database and store the remote items again — this could be improved.
Our first approach to this architecture was communicating the layers using callbacks. We had nested callbacks to communicate through the view to the interactor and back to the view again. Instead of using callbacks to communicate between layers we use the power of RxJava to provide the data upstream. Each of the inner layers can transform the data in a way the outer layer can understand it.
Presenters, interactors and repositories are unit tested by using JUnit and Mockito. They are all framework-agnostic components that use plain Java objects and do not reference any Android specific code, thus making unit tests really easy to build and maintain.
We use Espresso tests together with Mockito for integration and UI tests. Mockito allows us to mock the Observables returned from the repository to be able to test different states of the UI, like success, failure and progress.
We use Dagger 2 as a dependency injection framework which allows us to have control of the dependency graph and inject mocked dependencies while testing. We organize the dependencies in modules in a way they can be easily interchangeable with mock modules while testing.
We use the Clean Architecture approach for developing apps that are easy to maintain, test and — why not — to reuse the business logic in other projects. But, most importantly, we are doing a favor to future developers that are going to work on the code. Getting your hands on tightly coupled code — where you are afraid to change even one line of code — is painful! Through the Clean Architecture approach, we can lead the world of Android development to a happy place :).
One question we have asked and discussed with my colleagues is whether we can apply this architecture to all cases. My opinion is: yes. No matter how small the app is, it may grow in the future, so why not do things right from the beginning?
You may want to take a look at our example on GitHub where we use the Clean Architecture in a small app for pets.
- Clean architecture: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
- Dagger: https://dagger.dev/
- RxJava: https://github.com/ReactiveX/RxJava
- Espresso: https://developer.android.com/reference/android/support/test/espresso/Espresso.html
- Mockito: https://github.com/mockito/mockito
- Roboelectric: https://github.com/robolectric/robolectric
What are your thoughts on this architecture? Do you have any experience with it? Let us know.
In the next post, we are going to build a step-by-step application applying the Clean Architecture approach using Kotlin. Stay tuned!