Lesson 23: Combining Room and ViewModel
Combining Room and ViewModel
Let's make tasks genuinely persistent by connecting the Room database from lesson 22 to a ViewModel.
The ViewModel, observing the database directly
class TaskListViewModel(private val taskDao: TaskDao) : ViewModel() {
val tasks: StateFlow> = taskDao.getAllTasks()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = emptyList()
)
fun addTask(title: String) {
viewModelScope.launch {
taskDao.insertTask(TaskEntity(title = title, isDone = false))
}
}
}stateIn converts the DAO's Flow into a StateFlow Compose can observe directly — any time a row is inserted, updated, or deleted in the database, this list updates automatically, with no manual "refresh" call anywhere.
Collecting the Flow in your composable
@Composable
fun TaskListScreen(viewModel: TaskListViewModel) {
val tasks by viewModel.tasks.collectAsState()
LazyColumn {
items(tasks) { task ->
Text(task.title, modifier = Modifier.padding(16.dp))
}
}
}collectAsState() is the bridge between Kotlin Flows and Compose state — it converts the Flow into something Compose recomposes on automatically, same as mutableStateOf from lesson 12.
Why this pattern matters: a single source of truth
Notice what changed: the UI no longer holds "the list of tasks" as its own state at all — it just observes whatever's in the database. Add a task, and it appears. Restart the app, and it's still there. This is the single source of truth pattern real production apps use, and it's the same idea covered in the earlier System Design Fundamentals post on this blog, if you want to go deeper on why this matters at scale.
Next: Lesson 24 — requesting permissions, like access to the camera or notifications.