UI logic and data should live in a ViewModel, not the Activity/Fragment. ViewModels survive configuration changes (like rotation) and make code testable.
A ViewModel exposing state
class CounterViewModel : ViewModel() {
private val _count = MutableStateFlow(0)
val count: StateFlow<Int> = _count
fun increment() { _count.value++ }
}
Observing it
// Compose
val vm: CounterViewModel = viewModel()
val count by vm.count.collectAsState()
Text("$count")
// XML world: LiveData + observe(viewLifecycleOwner) { ... }
The pattern
- View tells the ViewModel about events (button clicks).
- ViewModel updates state and exposes it.
- View renders whatever the state currently is.
Common mistake: Never hold a reference to a View or Context (except applicationContext) in a ViewModel — it causes memory leaks.
Summary
ViewModels hold UI state that survives rotation. Expose immutable state (StateFlow/LiveData) and mutate it only inside the ViewModel.