Skip to content

Intermediate: Android basics · Navigation and app architecture · topic 29 of 50 · 30 min

App architecture: layers and repositories

Explanation

Google's recommended app architecture has a UI layer (composables and ViewModels), an optional domain layer (use cases for shared business logic) and a data layer (repositories and data sources). Repositories are the single source of truth for each kind of data and hide whether it comes from the network, a database or both. State flows down, events flow up.

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.

output
┌──────────────── 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)│
└──────────────────────────────────────────┘
Upper layers depend on lower layers, never the other way round.

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.

kotlin
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
    }
}
Screens read one consistent source (the database); the network only refreshes it.

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.

Think of it like this: Think of a big hospital. The reception (UI layer) talks to patients. Doctors (domain layer) apply medical rules. The records department (data layer) is the only place that stores and fetches files, whether from the computer or the archive room. Reception never walks into the archive itself; it asks records.

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

UI | Domain | Data

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.

kotlin
// 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
    }
}
operator fun invoke lets you call it like a function: calculateBill(items, 10).

Practice (0/3 done)

Task 1 · Question

Where should code that calls the Retrofit API live?

Task 2 · Question

What does "single source of truth" mean?

Task 3 · Practice

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

Question 1

1.Which layer is optional in Google's recommended architecture?

Question 2

2.What does a repository do?

Question 3

3.In unidirectional data flow, which way do events go?

Question 4

4.Should a repository return Compose Color or UI objects?

Read more (free)

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

Answer: A read-only StateFlow of UI state

Expose read-only state.

From ViewModel and MVVM

Which test annotation loads only the web layer for controller tests?Related

Answer: @WebMvcTest

@WebMvcTest is a test slice for MVC controllers.

From Layers: controller, service and repository

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

Answer: The Room database

The UI observes local data.

From Interview questions: Android and Compose

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. 1 more topic in “Navigation and app architecture” before its exam: Dependency injection with Hilt.
  2. Module 7 exam: Navigation and app architecture · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
  3. Then the next module, Saving data and calling APIs. 50 topics left in the course before the final exam.

← Previous: ViewModel and MVVM