State Hoisting & ViewModel

June 02, 2026 1 min read

State hoisting means moving state out of a composable to its caller, making the composable reusable and testable ("stateless"). For whole screens, state lives in a ViewModel.

Hoisted (stateless) composable

@Composable
fun SearchBar(query: String, onQueryChange: (String) -> Unit) {
    TextField(value = query, onValueChange = onQueryChange)
}
// caller owns the state and passes it down + handles changes

Screen state in a ViewModel

class FeedViewModel : ViewModel() {
    private val _posts = MutableStateFlow<List<Post>>(emptyList())
    val posts: StateFlow<List<Post>> = _posts
    fun load() { /* update _posts */ }
}

@Composable
fun FeedScreen(vm: FeedViewModel = viewModel()) {
    val posts by vm.posts.collectAsState()
    LazyColumn { items(posts) { Text(it.title) } }
}

Pattern: state flows down, events flow up (UDF — Unidirectional Data Flow).

Tip: Read StateFlow with collectAsState() (or collectAsStateWithLifecycle()) so the UI updates automatically.

Summary

Hoist state to make composables reusable; keep screen state in a ViewModel exposed as StateFlow. State down, events up.