Intermediate: Android basics · Navigation and app architecture · topic 29 of 50 · 30 min
App architecture: layers and repositories
Explanation
Why layers?
Small apps can live in a few files. Real apps grow: more screens, offline support, new APIs, more developers. Without structure, network code ends up inside screens, the same logic is copied in three places, and every change breaks something. Separating the app into layers with clear jobs keeps changes small and code testable.
┌──────────────── UI layer ────────────────┐
│ Composables <--state-- ViewModels │
│ --events--> │
└───────────────────┬──────────────────────┘
│ calls
┌───────── Domain layer (optional) ────────┐
│ Use cases: PlaceOrderUseCase, ... │
└───────────────────┬──────────────────────┘
│ calls
┌──────────────── Data layer ──────────────┐
│ Repositories: OrderRepository, ... │
│ ├── Remote data source (Retrofit API) │
│ └── Local data source (Room, DataStore)│
└──────────────────────────────────────────┘The UI layer
Composables show state and send events; ViewModels hold UI state and call the layers below. The UI layer should not know whether data came from a server or a database.
The data layer and repositories
A repository is a class that is the single entry point for one kind of data, such as orders or products. It decides where the data comes from (network, database, cache), combines and caches it, and exposes clean functions and flows. A data source is a lower-level class that talks to exactly one source, like the Retrofit API or the Room DAO.
class OrderRepository(
private val api: OrderApi, // remote data source
private val dao: OrderDao, // local data source
) {
// Single source of truth: the UI always reads from the database
val orders: Flow<List<Order>> = dao.observeAll()
suspend fun refresh() {
val fresh = api.getOrders() // network
dao.replaceAll(fresh) // save; the Flow above emits the new list
}
}Single source of truth (SSOT) means each piece of data has one owner that everyone reads from. Here the database is the SSOT for orders, so the list looks the same on every screen, works offline, and updates everywhere when it changes.
The optional domain layer
When several ViewModels need the same business logic (for example "apply coupon and calculate GST"), put it in a use case class with one public function, such as CalculateBillUseCase. Small apps can skip this layer; add it when you see duplication.
Unidirectional data flow
Across all layers, state flows down (database to repository to ViewModel to UI) and events flow up (UI to ViewModel to repository). Data is never changed from two directions at once, which removes a whole family of "why did this change?" bugs.
Common mistakes
- Calling Retrofit directly from a ViewModel or composable. Go through a repository.
- Repositories that return Android UI types (Color, painter). Keep the data layer free of UI.
- Several sources of truth (one list in memory, another in the database) that drift apart.
- Adding every layer and abstraction to a tiny app. Start simple; add layers as the app grows.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
App architecture: layers and repositories · 1/6
Layers keep a growing app manageable: UI, an optional domain layer, and data.
Prefer a video by a teacher? Find free videos on “android app architecture repository ui layer data layer domain layer” on YouTube ↗
In real life: Now in Android
Google publishes an open-source sample app called "Now in Android" that follows this architecture: feature modules with ViewModels and composables, a data layer with repositories backed by a local database and a network source, and an offline-first design. Reading parts of it after this module is a good way to see these layers in a real codebase.
// A use case shared by Checkout and Cart screens
class CalculateBillUseCase {
operator fun invoke(itemsPaise: List<Long>, couponPercent: Int): Long {
val subtotal = itemsPaise.sum()
val discounted = subtotal - subtotal * couponPercent / 100
return discounted + discounted * 18 / 100 // add 18% GST
}
}Practice (0/3 done)
Where should code that calls the Retrofit API live?
What does "single source of truth" mean?
Draw (on paper or in a text file) the layers for a "notes" app: NotesScreen, NotesViewModel, an optional SearchNotesUseCase, NotesRepository, NoteDao (Room) and NotesApi (sync). Draw arrows for state and events, and write one sentence for what each class is responsible for.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Dependency injection with HiltNavigation and app architecture · same module
- Navigation ComposeNavigation and app architecture · same module
- Layers: controller, service and repositoryJava Programming (Core to Spring Boot) · Spring Boot basics
- Interview questions: Android and ComposeAndroid career path
- Offline-first apps and DataStoreSaving data and calling APIs
- What full-stack meansFull-Stack Development · What full-stack development is
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
Which layer is optional in Google's recommended architecture?
Answer: Domain layer
Add a domain layer when logic is shared or complex.
What does a repository do?
Answer: Provides one kind of data to the app and hides where it comes from
It combines and owns data sources.
In unidirectional data flow, which way do events go?
Answer: Up from the UI to the ViewModel and below
Events go up, state comes down.
Should a repository return Compose Color or UI objects?
Answer: No, the data layer should not know about UI
Keep layers independent.
What does the ViewModel expose to the UI?Related
Which test annotation loads only the web layer for controller tests?Related
Answer: @WebMvcTest
@WebMvcTest is a test slice for MVC controllers.
What does "cold" mean for a Flow?Related
Answer: Its code runs only when collected, separately for each collector
Cold flows start on collect.
From Flow and StateFlow
What is the single source of truth in a typical offline-first list?Related
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 1 more topic in “Navigation and app architecture” before its exam: Dependency injection with Hilt.
- Module 7 exam: Navigation and app architecture · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
- Then the next module, Saving data and calling APIs. 50 topics left in the course before the final exam.