Skip to content

Clean Architecture for Android

By 2023, the words Clean Architecture already make me shake for some reason. Mainly, of course, this is a consequence of how they turned into a cargo cult for a large mass of people, I don't have that many questions for Uncle Bob himself. The other day in one of the Android channels my eye caught a repository from the author of the book Clean Architecture for Android (haven't read it, maybe it's fine, who knows, sounds hyped), where he tries to show us in Kotlin and with all modern approaches slash buzzwords how these Android apps should be written. Interesting, I thought, content.

In the readme we're immediately greeted by the holy trinity scalability, testability and flexibility. Sounds strong, can't argue, everything is needed. The app has 2 screens. One shows a response from a backend request, the second shows a list from the local database. How much code do you think? Right, 22 gradle modules, 7+ thousand lines of Kotlin code. Well okay, this is actually a classic technique, you write a very simple project, imagining you're writing a very complex project where all this is needed. I've done this myself a hundred times, there would be a point.

The saddest thing in such repositories is always that consciously or not, crucial decisions are bypassed with some micro-hacks or completely ignored. You over-engineer on one hand for a project of this scale, on the other hand you stick in some hack that's absolutely non-critical for a project of this scale. A normal app, if this isn't resolved - will start cracking at the seams.

It's quite difficult to find fault with "clean architecture" itself, well there are some layers there: data, domain, presentation, arrows in the right directions. But I would find fault with the implementation specifically as an Android app. Let's start with the simple, we made two dozen modules, probably there are some benefits for the build from this, right? No, they depend on each other quite chaotically. Features don't depend on each other in any direction, and that's a shame, this is the moment where your modular architecture is really tested for strength. That's where approaches like api/impl modules appear, to somehow parallelize the build of heavy impl, resolve circular dependencies and much more. There's none of that here. Scalability and Flexibility are under threat. So in the current form from multi-modularity there are only downsides, except for compile-time checking that no one violated our architecture. If all this was split into packages in one module, it would build faster, at minimum.

Gradle module management is generally mildly speaking so-so. When you copy-paste the Java version or compileSdk across dozens of modules, there's no smell of any extensibility or flexibility. Of special attention from me is the settings.gradle, which managed to turn an understandable module structure matching the file system into some barely comprehensible mess. Try to figure out where in the project are modules and where are just some folders inside which are modules. Even Android Studio goes crazy and doesn't show icons in the navigator.

The author uses Compose on one screen, on the second a Fragment with RecyclerView - this is the most interesting in this app, honestly, real life usually looks like this. But the place where the fragment is wrapped in Compose looks really bad, almost sure it will fall apart, there through reflection in the guts of FragmentManager some private method is called, what can we talk about here. RecyclerView without any wrappers over cells like AdapterDelegates scales poorly by itself. The screen that's on Compose - works with a ViewModel that lives in the shared Activity. So, by this logic, adding screens our number of fields with view models in Activity will grow, not to mention that the Activity-container knows about the implementation of specific features, which is not very clean. Why use Hilt I don't understand at all, Anvil exists if you really want to drag some DI magic to yourself. Hilt and multi-modularity are incompatible things. Almost all project dependencies live in Singleton scope and all Dagger modules are written in app, which doesn't really help project extensibility either. Jetpack Navigation for Compose already on two screens somehow looks like shit code, I don't know what's wrong with it.

It gets worse. Base classes with generics everywhere, BaseFragment, BaseViewModel, base UseCase and much more. I've never seen anyone write Base classes well, they sooner or later turn into a dump. For some reason, base classes are always a bunch of new abstract methods that need to be overridden, well-known lifecycle methods are hidden and a bunch of fields that try to predict what from all the variety of screens will be used. Spoiler - no one succeeds in guessing, there are some generic stubs, methods returning Unit in each class, and so on, well not to mention that quickly sticking some logic in a common base class is too big a temptation that most developers can't pass by. The author's BaseViewModel, by the way, doesn't inherit from ViewModel, so it's just a singleton at the fragment level. Sounds reliable as a Swiss watch.

My favorite moment here is that the suspend function execute in UseCase calls a callback, rather than just returning some value, as suspend functions usually do. This is literally what I'm talking about - the base class is written in such a way to satisfy all implementations (yes, there are several types of use case that inherit from the use case interface) instead of satisfying common sense. It would be better if there was no base class at all.

Conclusions - this is a well split into layers, but so-so project. You shouldn't write projects like this, this is a bad role model. If a person has their own book and GitHub looks like your mom's friend's son, that doesn't mean you should believe them, no offense. Look at how not to do it.