This project is a loose implementation of Clean Architecture as presented in my book, Clean Architecture for Android. It is a native Android project written in Kotlin. It demonstrates the key principles presented in the book and how they apply to a real life project.
I will endeavour to keep this project up to date and use it to demonstrate the strengths of the architecture: scalability, testability and flexibility when it comes to choosing 3rd party solutions.
- Feature separation
- Layer separation
- Data Source
- Unit tests
- End-to-end tests
- Demonstrates use of Jetpack Compose
- Demonstrates use of Coroutines including Flow
- Demonstrates MVVM
- Code quality checks using ktlint
- Code quality checks using detekt
- Continuous integration (CI) using GitHub Actions
- Unit tests
- Instrumentation tests
Mappers as classes vs. mapping extension functions
When mapping between models, we have several options. The primary decision is between mapper classes and mapping extension functions.
While extension functions are more concise, using them for mapping limits our choices of testing frameworks (Mockito, for example, cannot stub static functions).
How about injecting the mapper extension functions? We could do that. However, this removes the benefits of conciseness almost entirely. It also makes navigation to the implementation harder.
And so, I opted for the slightly more verbose concrete mapper classes.
Skipping Google’s Architecture Components
The greatest issue with Google’s Architecture Components is that they leak Android details into the Presentation layer. This prevents the Presentation layer from being truly UI agnostic.
Another issue with the Architecture Components is that they give too much responsibility to the ViewModel. They make it persist state it does not own, leading to potential data synchronization bugs.
For these reasons, while still following MVVM, this project relies on Kotlin Flows rather than LiveData, and implements pure ViewModels rather than Google’s.
My personal preference remains Mockito-Kotlin. I find the code easier to read and follow when using it. At the time of writing, judging by the number of stars on each repository, the industry seems to lean towards Mockk.
I was asked about using fakes. I have explored fakes, and found them to be overly verbose and too expensive to maintain.
Dependency injection framework
A critical part of most modern apps, dependency injection (DI) helps us obtain the objects that build our app. It also helps manage their scope. The most popular choices in the Android world are Hilt (which is built on top of Dagger) and Koin.
Hilt was chosen for two main reasons:
- Compile time safety – having the confidence that all my dependencies are provided before the app starts is a huge time saver and helps maintain a stable app.
- Simplicity – from experience, setting up and using Hilt (unlike the underlying Dagger) is considerably easier than using Koin. Hilt also introduces fewer breaking changes over time.
XML vs Jetpack Compose
Why not both? I still have a lot of concerns around Jetpack Compose. Even so, it was important to me to show the presented architecture works well regardless of the UI mechanism chosen. As an exercise, I invite you to try and replace the UI layer from Compose to XML or vice versa without updating the Presentation layer.
Contributions to this project are welcome. Please feel free to report any issues or fork to make changes and raise a pull request.
Mobile App Development Best Practices – 03.10
iOS MetaCodable – Supercharge Swift’s Codable implementations with macros meta-programming How to build a Tuist plugin and publish it using...
How to make and use BOM (Bill of Materials) dependencies in Android projects
By using a BOM dependency, you can avoid specifying the versions of each individual library in your app, and let...
Telegram turns 10 years old and revenues stagnate
Telegram seems to want to grow not only through messaging but also through communities, which pretty much means it wants...
MetaCodable – Supercharge Swift’s Codable implementations with macros meta-programming
Supercharge Swift‘s Codable implementations with macros. Overview MetaCodable framework exposes custom macros which can be used to generate dynamic Codable implementations. The core of the framework...
How to get started with Swift Concurrency 🧵 (Beginner Tutorial)
Swift has built-in support for writing asynchronous and parallel code in a structured way. Asynchronous code can be suspended and resumed later,...
Mobile App Development Best Practices – 02.10
Data.ai has summarized the interim results of the year – and once again we have a record. Annual consumer spending...