State Management in Compose

Guide to managing state in Jetpack Compose with StateFlow.


ViewModel + StateFlow Pattern

// ViewModel
class MyViewModel : ViewModel() {
    private val _uiState = MutableStateFlow(UiState())
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()
 
    fun updateState() {
        _uiState.update { it.copy(isLoading = true) }
    }
}
 
// Composable
@Composable
fun MyScreen(viewModel: MyViewModel = viewModel()) {
    val state by viewModel.uiState.collectAsState()
 
    if (state.isLoading) {
        LoadingIndicator()
    }
}

Local UI State

@Composable
fun MyScreen() {
    // For simple UI-only state
    var selectedTab by remember { mutableStateOf(0) }
 
    TabRow(selectedTabIndex = selectedTab) {
        Tab(selected = selectedTab == 0, onClick = { selectedTab = 0 })
        Tab(selected = selectedTab == 1, onClick = { selectedTab = 1 })
    }
}

State Hoisting

@Composable
fun ParentScreen() {
    var text by remember { mutableStateOf("") }
 
    ChildComponent(
        text = text,
        onTextChange = { text = it }
    )
}
 
@Composable
fun ChildComponent(
    text: String,
    onTextChange: (String) -> Unit
) {
    TextField(
        value = text,
        onValueChange = onTextChange
    )
}

Side Effects

// LaunchedEffect: Run when key changes
LaunchedEffect(userId) {
    viewModel.loadUser(userId)
}
 
// DisposableEffect: Cleanup
DisposableEffect(Unit) {
    val listener = createListener()
    onDispose { listener.remove() }
}
 
// SideEffect: Run after every recomposition
SideEffect {
    analytics.trackScreenView()
}


Last Updated: 2025-11-01