State lives at the lowest common owner; pass state + events down.
@Composable
fun TodoList(items: List<String>, onAdd: (String) -> Unit) {
var text by remember { mutableStateOf("") }
Column {
Row { TextField(text, onValueChange = { text = it }); Button({ onAdd(text); text = "" }) { Text("Add") } }
LazyColumn { items(items) { Text(it) } }
}
}
Hoisted TodoList
used by a screen that delegates to a ViewModel.
// ui/TodoList.kt (stateless)
@Composable
fun TodoList(items: List<String>, onAdd: (String) -> Unit, modifier: Modifier = Modifier) {
var text by remember { mutableStateOf("") }
Column(modifier) {
Row { TextField(text, onValueChange = { text = it }); Button({ onAdd(text); text = "" }) { Text("Add") } }
LazyColumn { items(items) { Text(it) } }
}
}
// ui/TodoVm.kt
@HiltViewModel
class TodoVm @Inject constructor(): ViewModel() {
private val _items = MutableStateFlow(listOf<String>())
val items: StateFlow<List<String>> = _items.asStateFlow()
fun add(s: String) { if (s.isNotBlank()) _items.value = _items.value + s }
}
// ui/TodoScreen.kt
@Composable
fun TodoScreen(vm: TodoVm = hiltViewModel()) {
val items by vm.items.collectAsStateWithLifecycle()
TodoList(items = items, onAdd = vm::add, modifier = Modifier.padding(16.dp))
}
Notes
Paste into Android Studio project (see sandboxes/android-compose):