Let's think about it this way for a second
A real app is usually split into layers — the UI layer (Composable screens), the ViewModel layer (business logic, state management), and the Data layer (Room database, DAO). Every CRUD operation in the Todo app — adding a new todo (Create), viewing the list (Read), toggling done/undone (Update), and deleting an item (Delete) — needs to be wired through all three layers.
Let's connect this to a real scenario
Build the three layers one at a time: `TodoEntity`/`TodoDao`/`AppDatabase` (data layer) → `TodoViewModel` (observes the DAO via Flow, with add/toggle/delete functions) → `TodoListScreen`/`AddTodoScreen` (UI layer, LazyColumn + Navigation) — wire them so Navigation Compose lets you move from the list screen to the add screen.
Let's look at it together
// Full flow: UI calls ViewModel, ViewModel calls DAO
@Composable
fun TodoListScreen(viewModel: TodoViewModel = hiltViewModel(), onAddClick: () -> Unit) {
val todos by viewModel.todos.collectAsState()
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = onAddClick) { Icon(Icons.Default.Add, null) }
}
) { padding ->
LazyColumn(modifier = Modifier.padding(padding)) {
items(todos) { todo ->
Row {
Checkbox(
checked = todo.isDone,
onCheckedChange = { viewModel.toggleDone(todo.id) }
)
Text(todo.title)
IconButton(onClick = { viewModel.delete(todo.id) }) {
Icon(Icons.Default.Delete, null)
}
}
}
}
}
}Run the Todo app and you should be able to add a todo, toggle it done/undone, delete it, and see the data still there after restarting the app.Try it in 5 minutes
Build the whole Todo app yourself using the three layers (Data/ViewModel/UI) — verify all four CRUD operations work on the emulator.
One thing to watch out for
If you delete a todo item permanently with no 'undo' option, that's rough on the user — consider adding a Snackbar with an 'Undo' button instead.