Compose State Management: Complete Guide from remember to UiState
Jetpack Compose state management can feel overwhelming. Should you use remember, ViewModel, StateFlow? Let me break down every approach with clear use cases.
1. remember & mutableStateOf: Local UI State
Use this for simple, temporary UI state that doesn't survive configuration changes:
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Column {
Text("Count: $count")
Button(onClick = { count++ }) {
Text("Increment")
}
}
}
When: Toggle visibility, form inputs, temp animation values
Avoid: Data that should survive rotation or app kill
2. rememberSaveable: Surviving Config Changes
Automatically saves state to Bundle during configuration changes:
@Composable
fun FormScreen() {
var name by rememberSaveable { mutableStateOf("") }
var email by rememberSaveable { mutableStateOf("") }
// State persists across rotation
TextField(value = name, onValueChange = { name = it })
}
When: Form inputs, user preferences, temporary selections
3. State Hoisting: Composable Reusability
Move state up to make composables reusable:
@Composable
fun Button(value: String, onValueChange: (String) -> Unit) {
// Stateless button
}
@Composable
fun Container() {
var state by remember { mutableStateOf("") }
Button(value = state, onValueChange = { state = it })
}
When: Building component libraries, testing without mocks
4. ViewModel + StateFlow: Persistent Screen State
For business logic and data that survives app kill:
class UserViewModel : ViewModel() {
private val _userData = MutableStateFlow<User?>(null)
val userData: StateFlow<User?> = _userData.asStateFlow()
init {
viewModelScope.launch {
_userData.value = userRepository.getUser()
}
}
}
@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
val user by viewModel.userData.collectAsStateWithLifecycle()
Text("${user?.name}")
}
When: API calls, database queries, persistent user data
5. UiState Sealed Class: Handling Loading/Error/Success
sealed class UserUiState {
object Loading : UserUiState()
data class Success(val user: User) : UserUiState()
data class Error(val exception: Exception) : UserUiState()
}
class UserViewModel : ViewModel() {
private val _uiState = MutableStateFlow<UserUiState>(UserUiState.Loading)
val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
fun loadUser() {
viewModelScope.launch {
try {
val user = repository.getUser()
_uiState.value = UserUiState.Success(user)
} catch (e: Exception) {
_uiState.value = UserUiState.Error(e)
}
}
}
}
@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
when (uiState) {
is UserUiState.Loading -> CircularProgressIndicator()
is UserUiState.Success -> UserCard((uiState as UserUiState.Success).user)
is UserUiState.Error -> ErrorMessage((uiState as UserUiState.Error).exception)
}
}
When: Any screen with async operations
6. derivedStateOf: Computed Values
Avoid recompositions when intermediate values change:
@Composable
fun List(items: List<String>) {
val filteredItems by remember(items) {
derivedStateOf { items.filter { it.length > 3 } }
}
// Recomposes only when filtered list actually changes
}
When: Complex filters, expensive calculations on state
7. collectAsStateWithLifecycle: The Safe Collector
Automatically handles lifecycle to prevent memory leaks:
@Composable
fun Screen(viewModel: MyViewModel = viewModel()) {
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
// Safely observes without manual lifecycle management
}
When: Always use this instead of collect in Compose
Comparison Table
| Approach | Scope | Survives Rotation | Survives Kill | Async Support | Best For |
|---|---|---|---|---|---|
remember |
Composition | ❌ | ❌ | ❌ | UI toggles, animations |
rememberSaveable |
Composition | ✅ | ❌ | ❌ | Form inputs |
ViewModel+StateFlow |
Screen | ✅ | ✅ | ✅ | Business logic & data |
UiState pattern |
Screen | ✅ | ✅ | ✅ | Async operations |
The Golden Rule
-
Simple UI state →
remember -
Persist across rotation →
rememberSaveable -
API calls, database →
ViewModel + StateFlow -
Loading/Error/Success →
UiState sealed class
Master this hierarchy and your Compose architecture will be solid.
Ready to build production Android apps?
8 Android App Templates → https://myougatheax.gumroad.com
Top comments (0)