What you'll learn
You have all the pieces — composables, state, ViewModels. Architecture is how you arrange them so an app stays understandable as it grows to dozens of screens. Android guidance recommends a UI layer, a data layer, state holders, and unidirectional data flow.MVVM is a useful vocabulary for these responsibilities, rather than a mandatory framework.
By the end you'll be able to:
- Explain why architecture matters
- Describe the Model–View–ViewModel layers and their jobs
- Apply unidirectional data flow across a whole screen
- Structure a real app into layers and packages
Why architecture matters
A small app with everything in one file works — until it doesn't. Without structure, UI code, business rules, and data access tangle together: a change in one place breaks another, nothing can be tested in isolation, and two people can't work without colliding. Architecture is just a set of rules for where code goes, giving you separation of concerns — each part has one job and one reason to change.
The MVVM layers
MVVM splits a screen into three responsibilities:
| Layer | Is | Job |
|---|---|---|
View | Your composables | Render state, manage UI behavior, and forward business events. |
ViewModel | A ViewModel | Hold UI state; run logic; call the data layer. |
Model | Repository + data sources | Own the data — network, database, files. |
The ViewModel is the middle layer — it turns raw data from the Model into UI state for the View:
class TaskListViewModel(
private val repository: TaskRepository, // depends on an abstraction
) : ViewModel() {
// TaskRepository exposes a Flow<List<Task>> named tasks.
val uiState: StateFlow<TaskListUiState> = repository.tasks
.map { tasks -> TaskListUiState(tasks = tasks) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), TaskListUiState())
fun addTask(title: String) {
viewModelScope.launch {
repository.add(Task(title = title)) // delegate data work
}
}
}And the View stays thin — collect the state, render it, forward events. If you see an if deciding business rules inside a composable, it probably belongs in the ViewModel:
@Composable
fun TaskListScreen(viewModel: TaskListViewModel) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
// The View: renders state, forwards events. No business logic here.
TaskListContent(
state = uiState,
onAddTask = viewModel::addTask,
)
}Key idea
Unidirectional data flow
Everything you've learned converges here. State flows down — the ViewModel exposes UI state, the View renders it. Events flow up — the View calls ViewModel functions, which update the state, which flows back down. A single, predictable loop:
Because data only moves in this one loop, you can always answer "why does the screen look like this?" — it's a function of the current UI state, and the only way state changed is through a ViewModel function. No hidden mutations, no surprises.
Structuring a real app
A common, scalable package layout groups code by layer (and, in bigger apps, by feature):
com.example.tasks/
├── data/
│ ├── local/ Room database, DAOs
│ ├── remote/ Retrofit API
│ └── TaskRepository.kt
├── ui/
│ ├── tasklist/ TaskListScreen + TaskListViewModel
│ ├── taskdetail/ TaskDetailScreen + TaskDetailViewModel
│ └── theme/ Color, Type, Theme
└── MainActivity.ktTip
data/ layer you just outlined.Practice: one small working feature
Separate a screen, a ViewModel, and a small in-memory repository.
Check your result
The screen emits an event; the ViewModel calls the repository and exposes the resulting state.
Remember
Recap & quick check
Key takeaways
- Architecture gives separation of concerns — each part has one job and one reason to change.
- MVVM: View (composables) displays state; ViewModel holds state and logic; Model (repository) owns data.
- Dependencies point one way: View → ViewModel → Model; lower layers never know about higher ones.
- Unidirectional data flow: state flows down, events flow up, in one predictable loop.
- Structure code by layer (data/ and ui/), grouping each screen's composable with its ViewModel.
Quick check
1. In MVVM, what is the 'View'?
2. Which way do the layer dependencies point?
3. A composable contains a complex business rule. Where should it go?
4. What is unidirectional data flow?
5. A benefit of separation of concerns is…
Continue with Lifecycle & State Restoration, then build the Phase 3 capstone to combine navigation, ViewModel state, and repository boundaries. Phase 4 then develops the data layer with coroutines, Flow, DataStore, Room, and Retrofit.