App Architecture: MVVM, Repository & Flow
Why architecture matters
When you put networking, database and UI code all inside an Activity, the file becomes huge, bugs multiply, and data is lost on rotation. A clear architecture splits responsibilities so each part has one job. Google recommends MVVM with a Repository.
- View (Activity/Fragment/Compose) — only displays state and forwards user events.
- ViewModel — holds UI state and logic; survives rotation.
- Repository — the single source of truth; decides whether data comes from network or database.
- Data sources — Retrofit (network) and Room (local).
The Repository
class UserRepository(
private val api: ApiService,
private val dao: UserDao
) {
fun observeUsers(): Flow<List<User>> = dao.observeAll()
suspend fun refresh() {
val fresh = api.getUsers() // from network
dao.insertAll(fresh) // cache locally
}
}
The ViewModel exposes a single state
class UsersViewModel(private val repo: UserRepository) : ViewModel() {
val users: StateFlow<List<User>> = repo.observeUsers()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
fun refresh() = viewModelScope.launch { repo.refresh() }
}
The UI just observes
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.users.collect { list -> adapter.submitList(list) }
}
}
Common mistakes
- Calling the network directly from the Activity — route it through ViewModel → Repository.
- Exposing mutable state — expose read-only
StateFlow, keep the mutable one private. - Collecting flows without
repeatOnLifecycle— it wastes resources when the screen is hidden.
Summary: View → ViewModel → Repository → (Retrofit + Room). One job per layer makes apps testable, rotation-safe, and easy to grow.