Let's think about it this way for a second
A Coroutine is basically a lightweight thread — calling a `suspend` function inside `viewModelScope.launch { }` runs it in the background without blocking the UI thread, then updates the UI once the result comes back. Flow represents 'a stream that keeps emitting values over time' (an auto-updating database query result, a real-time search box) — write a Room DAO query with a `Flow<List<TodoEntity>>` return type and the UI updates automatically whenever the underlying data changes.
Let's connect this to a real scenario
If you write `@Query("SELECT * FROM TodoEntity") fun getAllAsFlow(): Flow<List<TodoEntity>>` in your Room DAO — whenever a new todo item gets inserted (even from other code), the `.collect { todos -> ... }` in your ViewModel triggers automatically and live-updates the UI — no manual refresh button needed.
Let's walk through it together
class TodoViewModel(private val dao: TodoDao) : ViewModel() {
val todos: StateFlow<List<TodoEntity>> = dao.getAllAsFlow()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = emptyList()
)
fun addTodo(title: String) {
viewModelScope.launch {
dao.insert(TodoEntity(id = UUID.randomUUID().toString(), title = title, isDone = false))
}
}
}Calling `addTodo()` should update the database, and you should see the todos list update automatically without needing a manual UI refresh.5-minute try-it
Rewrite the Room DAO (Advanced lesson 3) to return `Flow<List<TodoEntity>>` — confirm that inserting a todo item auto-updates the UI without a manual refresh.
A quick word of caution
Don't use `GlobalScope.launch { }` in production code — you lose control over the Coroutine's lifecycle, risking memory leaks/crashes; always use `viewModelScope`/`lifecycleScope` instead.