Room Database
๐๏ธ Room Database
๐ @Entity โ A Table, as a Data Class
An @Entity is an ordinary data class (Kotlin Fundamentals Chapter 4) with annotations describing how it maps to a database table โ each constructor property becomes a column, and @PrimaryKey(autoGenerate = true) means Room assigns each new row's id automatically, so id = 0 on a new Task is just a placeholder before insertion.
๐ @Dao โ The Query Interface
A DAO (Data Access Object) is an interface โ Room generates the actual implementation at compile time, from annotated method signatures and SQL:
Flow-Returning Queries โ Live Data
getAllTasks(): Flow<List<Task>> doesn't run once โ Room re-emits automatically whenever the underlying tasks table changes, so a collector always sees the current data without ever manually re-querying. This is Kotlin Intermediate Chapter 2's Flow, produced by Room itself.
suspend Functions โ Coroutine-Native Writes
insert/update/delete are suspend functions (Kotlin Intermediate Chapter 1) โ calling them from a coroutine automatically runs the actual disk I/O off the main thread, with none of the manual thread-management older Android database code needed.
The string inside @Query(...) is genuinely validated against the @Entity classes it references while compiling โ a typo'd column name or a query that doesn't match the DAO method's return type is a compile error, not something that only surfaces as a runtime crash. This is one of Room's biggest practical advantages over writing raw SQLite calls by hand.
๐๏ธ @Database โ Tying It Together
@Database(entities = [Task::class], version = 1) lists every entity the database contains, and a version number Room uses to detect when a schema migration is needed (covered more fully in a later production-focused chapter). The companion object singleton pattern (Kotlin Fundamentals Chapter 4) ensures the whole app shares one AppDatabase instance โ creating a fresh one per screen would be wasteful and could cause data inconsistency between them.
๐ Wiring Room Into a ViewModel
This is where Chapter 2's StateFlow pattern and this chapter's DAO Flow meet โ a DAO's Flow converts directly into a ViewModel-exposed StateFlow:
viewModelScope is a CoroutineScope (Kotlin Intermediate Chapter 1) tied to the ViewModel's own lifetime โ any coroutine launched in it is automatically cancelled when the ViewModel is cleared, so addTask's launch never needs manual cleanup. .stateIn(...) converts the DAO's cold Flow into a hot, shareable StateFlow โ WhileSubscribed(5000) keeps it active for 5 seconds after the last collector disappears (surviving a brief rotation gap) before actually stopping the underlying query.
Room vs a Backend ORM
| Node.js + mysql2 (Node Course 2) | Room | |
|---|---|---|
| Where the database lives | A remote server | On-device, embedded (SQLite) |
| Query validation | Runtime (bad SQL fails when it runs) | Compile time |
| Live-updating results | Not automatic โ re-query manually | Flow-returning queries update automatically |
| Async model | Promises / async-await | Kotlin suspend functions / Flow |
๐ป Coding Challenges
Challenge 1: Entity, DAO, and Database
Define a Note entity (id, title, content), a NoteDao interface with a Flow-returning getAllNotes() query and a suspend insert() function, and an AppDatabase abstract class wiring them together with the companion-object singleton pattern.
Goal: Practice the three-piece Room setup from scratch.
Challenge 2: A ViewModel Backed by Room
Write a NoteViewModel that exposes Challenge 1's getAllNotes() as a StateFlow via stateIn (with viewModelScope and WhileSubscribed(5000)), and an addNote(title: String, content: String) function that inserts via viewModelScope.launch.
Goal: Practice connecting a DAO's Flow to a ViewModel-exposed StateFlow, and a suspend write via viewModelScope.
Challenge 3: A Query with a WHERE Clause
Add a DAO function fun searchNotesByTitle(query: String): Flow<List<Note>> using @Query with a SQL WHERE title LIKE :query clause (using SQLite's % wildcard). Add a comment explaining what happens if the parameter name in the method signature doesn't match the :placeholder name in the query string.
Goal: Practice a parameterized @Query beyond the simple SELECT * examples in the chapter.
Room's Flow, the ViewModel's StateFlow, and Compose's collectAsStateWithLifecycle() form one continuous pipe: change the database, and the UI updates itself automatically, with no manual "refresh" step anywhere in that chain. This three-layer pattern โ data layer emits Flow, ViewModel exposes StateFlow, UI collects it โ is close to universal across real, modern Android apps, not specific to this course's example.
๐ฏ What's Next
Next chapter: Networking with Retrofit โ REST calls, Gson/Moshi, error handling, and loading states.