Architecture Guide
This section is the reference companion to the curriculum modules. While each module teaches one slice of the stack, this guide steps back and shows how those slices fit together in a production codebase.
The big picture
A modern Android app is a layered, reactive, dependency-injected system:
Three principles drive every decision:
Single Source of Truth
For every piece of data, one place owns it. Everyone else observes.
Reactive Streams
State propagates automatically via Flow / StateFlow — no manual notification.
Dependency Inversion
Outer layers depend on inner abstractions. Domain depends on no Android, no library.
Pick the right architecture for the project
Not every app needs full Clean Architecture. Be honest about the size:
| App size | Recommended architecture |
|---|---|
| Hackathon / proof of concept | MVVM + Repository, single module |
| Indie app, < 20 screens | MVVM + Repository + Hilt, optional :domain module |
| Production, 20+ screens, team | Clean Architecture + modularization + KMP-ready |
| Multi-platform (Android + iOS) | Clean Architecture + KMP :shared module |
Over-engineering is the most common mistake. A use case that just calls one repository method adds boilerplate with no benefit. Start lean, evolve toward Clean Architecture as the codebase grows.
State as the contract
A screen has one UI state class that fully describes what to render:
data class ProductDetailUiState(
val isLoading: Boolean = false,
val product: Product? = null,
val isInWishlist: Boolean = false,
val error: String? = null
)
The View renders deterministically from this state. The ViewModel updates it. Tests assert the state transitions. Nothing else changes UI.
For events (one-shot — navigation, snackbar, toast), use a separate
SharedFlow<Event>. Don't put events in state — they'll replay on rotation.
Continue reading
- Clean Architecture — the layered Domain/Data/Presentation breakdown
- Project Structure — Gradle modules, package layout, naming conventions