Compose has no concept of mutable fields driving the UI directly — state must be explicit and observable. remember preserves a value across recompositions (but not configuration changes, for which you’d use rememberSaveable):
@Composable
fun SearchScreen(viewModel: SearchViewModel = hiltViewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
var query by rememberSaveable { mutableStateOf("") }
Column {
OutlinedTextField(
value = query,
onValueChange = { newValue ->
query = newValue
viewModel.search(newValue)
},
label = { Text("Search movies") }
)
when (uiState) {
is UiState.Loading -> CircularProgressIndicator()
is UiState.Success -> MovieList((uiState as UiState.Success).movies)
is UiState.Error -> Text((uiState as UiState.Error).message)
UiState.Idle -> Text("Start typing to search")
}
}
}
State hoisting is the pattern of moving state up to the caller (here, to the ViewModel) rather than storing it inside a low-level composable like MovieCard — this keeps composables reusable, testable, and predictable, since they simply render whatever state they’re given.
collectAsStateWithLifecycle() is the correct way to collect a StateFlow in Compose — it automatically pauses collection when the screen isn’t visible, avoiding unnecessary work.
- Image
- SKU
- Rating
- Price
- Stock
- Availability
- Add to cart
- Description
- Content
- Weight
- Dimensions
- Additional information