mutableStateOf and MutableStateFlow both hold a value and notify observers when it changes, and both are common choices for state in a Compose app. They’re not interchangeable, though — they belong at different layers of your app.
mutableStateOf: Compose’s Own Observable Type
mutableStateOf creates a MutableState<T>, which is directly understood by the Compose compiler. Reading .value (or using the by delegate) inside a composable automatically subscribes that composable to changes — no extra collection step required.
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("$count")
}
It only exists inside the Compose runtime. It has no concept of coroutines, has no operators like map or filter, and can’t be read from plain Kotlin code outside a composable in any meaningful way.
StateFlow: A General-Purpose Observable
StateFlow is part of Kotlin coroutines, not Compose. It works anywhere Kotlin runs — a ViewModel, a repository, plain business logic — with no dependency on the UI framework. To read one inside a composable, you convert it with collectAsStateWithLifecycle() (or collectAsState()), which bridges it into a Compose State.
class CounterViewModel : ViewModel() {
private val _count = MutableStateFlow(0)
val count: StateFlow<Int> = _count.asStateFlow()
fun increment() { _count.value++ }
}
@Composable
fun CounterScreen(viewModel: CounterViewModel) {
val count by viewModel.count.collectAsStateWithLifecycle()
Button(onClick = viewModel::increment) {
Text("$count")
}
}
The Practical Difference
- mutableStateOf lives and dies with the Composition. It’s gone when the composable leaves the screen, unless wrapped in
rememberSaveable. Use it for state that’s purely about how this screen looks right now — an expanded/collapsed flag, a text field’s current value before it’s submitted. - StateFlow lives as long as whatever holds it — typically a
ViewModel, which survives configuration changes on its own. Use it for state that represents your app’s actual data: the fetched user profile, the list of items from an API, anything business logic needs to read or update independent of the UI.
A Simple Rule
If the state needs to be tested without rendering a composable, shared between screens, or survive independent of the Composition, put it in a StateFlow inside a ViewModel. If it’s purely transient UI presentation state scoped to one composable, mutableStateOf with remember is simpler and has less overhead.