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.