State is any value that can change over time. In Compose, state controls what the UI displays.
Examples of state:
- text typed into a field
- whether a dialog is visible
- loading, success, or error state
- selected tab
- list of items from an API
Remember
Use remember for local UI state that should survive recomposition.
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("Clicked $count times")
}
}
Without remember, the value would reset during recomposition.
State Hoisting
State hoisting means moving state up and passing values and events down.
@Composable
fun NameInput(
name: String,
onNameChange: (String) -> Unit
) {
TextField(
value = name,
onValueChange = onNameChange
)
}
This makes the composable reusable and easier to test.
ViewModel State
For screen-level state, use a ViewModel.
data class LoginUiState(
val email: String = "",
val password: String = "",
val isLoading: Boolean = false
)
The ViewModel owns the state. The composable observes it and sends events.
Avoid These Mistakes
| Mistake | Better approach |
|---|---|
| Storing business logic in composables | Put it in ViewModel/use cases |
| Duplicating the same state in multiple places | Have one source of truth |
| Mutating lists directly | Create a new list |
Using remember for data that must survive process death | Use ViewModel and saved state |
Practice
Build a small login form with email and password fields. Keep the text state in the parent composable and pass it down to reusable input composables.
Summary
Compose UI is a function of state. Keep local state local, hoist reusable state, and use ViewModels for screen-level behavior.