Room Database

Android Development โ€” Architecture & Data
Course 2 ยท Chapter 3 ยท Room Database

๐Ÿ—„๏ธ Room Database

Room is Android's standard local database library โ€” a layer over SQLite that turns raw SQL into typed Kotlin classes and functions, checked at compile time. This chapter covers the three pieces every Room setup needs (Entity, DAO, Database), and how they connect to the ViewModel/StateFlow pattern from Chapter 2 to keep the UI in sync with what's actually stored on disk.

๐Ÿ“‹ @Entity โ€” A Table, as a Data Class

@Entity(tableName = "tasks") data class Task( @PrimaryKey(autoGenerate = true) val id: Int = 0, val title: String, val isDone: Boolean = false )

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:

@Dao interface TaskDao { @Query("SELECT * FROM tasks ORDER BY id DESC") fun getAllTasks(): Flow<List<Task>> @Insert suspend fun insert(task: Task) @Update suspend fun update(task: Task) @Delete suspend fun delete(task: Task) }

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.

โš  Room Checks Your SQL at Compile Time

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) abstract class AppDatabase : RoomDatabase() { abstract fun taskDao(): TaskDao companion object { @Volatile private var INSTANCE: AppDatabase? = null fun getDatabase(context: Context): AppDatabase { return INSTANCE ?: synchronized(this) { Room.databaseBuilder(context, AppDatabase::class.java, "app_database") .build() .also { INSTANCE = it } } } } }

@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:

class TaskViewModel(private val dao: TaskDao) : ViewModel() { val tasks: StateFlow<List<Task>> = dao.getAllTasks() .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = emptyList() ) fun addTask(title: String) { viewModelScope.launch { dao.insert(Task(title = title)) } } }

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 livesA remote serverOn-device, embedded (SQLite)
Query validationRuntime (bad SQL fails when it runs)Compile time
Live-updating resultsNot automatic โ€” re-query manuallyFlow-returning queries update automatically
Async modelPromises / async-awaitKotlin 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.

โ†’ Solution

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.

โ†’ Solution

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.

โ†’ Solution

๐Ÿ’ก The Flow โ†’ StateFlow โ†’ Compose Chain Is the Whole Architecture

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.