Compose State Management Patterns: Unidirectional Data Flow Explained
State ManagementArchitecture PatternsBest Practices
The declarative nature of Compose requires us to rethink data flow. Whether using ViewModel+StateFlow or frameworks like Orbit and Mavericks, the core goal is to keep state changes within predictable bounds.
Comparing Three Common Patterns
- ViewModel + StateFlow: Official recommended approach, suitable for most medium-sized projects;
- Unidirectional Data Flow (UDF): Emphasizes one-way input/output, easy to use with test snapshots;
- Redux/MVI: Event-driven, suitable for complex interactions or scenarios requiring time-travel debugging.
Note: Always avoid holding mutable state directly inside Composables, otherwise it increases the risk of recomposition inconsistencies.
Example: Filter Panel
The following code demonstrates how to centralize intent handling in the ViewModel and push results to the UI layer through immutable State:
@Stable
data class FilterState(
val categories: List<String> = emptyList(),
val selected: Set<String> = emptySet()
)
sealed interface FilterIntent {
data class Toggle(val category: String) : FilterIntent
data object Reset : FilterIntent
}
class FilterViewModel : ViewModel() {
private val _state = MutableStateFlow(FilterState())
val state: StateFlow<FilterState> = _state.asStateFlow()
fun dispatch(intent: FilterIntent) {
_state.update { current ->
when (intent) {
is FilterIntent.Toggle -> current.copy(
selected = current.selected.toggle(intent.category)
)
FilterIntent.Reset -> current.copy(selected = emptySet())
}
}
}
}