Intermediate: Android basics · Saving data and calling APIs · topic 31 of 50 · 35 min
Room database
Explanation
Why a local database?
Many apps must remember data between launches and work without internet: an expense tracker, a notes app, a cached product catalogue. SQLite is a small database engine built into every Android phone. Writing raw SQLite code is long and error-prone, so Google provides Room, a library on top of SQLite that checks your SQL queries at compile time and maps rows to Kotlin objects.
| Piece | What it is |
|---|---|
| @Entity | A data class that becomes a table; each property is a column |
| @Dao | An interface with functions for reading and writing; Room generates the code |
| @Database | An abstract class listing the entities and giving access to the DAOs |
Entity
@Entity(tableName = "expenses")
data class Expense(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val title: String,
val amountPaise: Long,
val category: String,
val createdAt: Long = System.currentTimeMillis(),
)DAO
@Dao
interface ExpenseDao {
@Query("SELECT * FROM expenses ORDER BY createdAt DESC")
fun observeAll(): Flow<List<Expense>> // live updates
@Query("SELECT SUM(amountPaise) FROM expenses WHERE category = :category")
fun totalFor(category: String): Flow<Long?>
@Insert
suspend fun insert(expense: Expense)
@Update
suspend fun update(expense: Expense)
@Delete
suspend fun delete(expense: Expense)
}Two styles of functions: suspend functions do one piece of work (insert, update, a single read) and Room runs them off the main thread. Functions returning Flow emit the current result and then a new result every time the table changes, which is perfect for a list screen that should update itself after an insert.
Database
@Database(entities = [Expense::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun expenseDao(): ExpenseDao
}
// Create ONE instance for the whole app (for example in a Hilt module or an app container):
val db = Room.databaseBuilder(applicationContext, AppDatabase::class.java, "app.db").build()Room generates code at build time using KSP (Kotlin Symbol Processing). Add the Room runtime, room-ktx and the room-compiler with ksp(...) in your Gradle file; the official Room guide shows the current setup. Older tutorials use kapt, which is in maintenance mode; prefer KSP.
Migrations
When you change an entity (add a column), you must increase the database version and tell Room how to move existing users' data: a Migration with the SQL to run, or an AutoMigration for simple changes. fallbackToDestructiveMigration() simply deletes and recreates the database, which loses user data; never ship it for important data without thinking.
Common mistakes
- Running database calls on the main thread: Room refuses (IllegalStateException). Use suspend functions or Flow.
- Creating a new database instance on every screen. Use a single shared instance.
- Changing an entity without increasing the version and adding a migration: the app crashes on update for existing users.
- Storing money as Double. Store paise as Long.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Room database · 1/6
Room is Android's recommended library for a local SQLite database. It checks your queries at compile time.
Prefer a video by a teacher? Find free videos on “android room database tutorial entity dao flow kotlin” on YouTube ↗
In real life: A personal expense tracker
An expense tracker stores every expense in Room. The home screen shows a live list from observeAll() and the monthly food total from totalFor("Food"). When the user adds an expense, the ViewModel calls insert; both the list and the total update by themselves, because they are Flows backed by the table. Everything works in airplane mode.
class ExpenseViewModel(private val dao: ExpenseDao) : ViewModel() {
val expenses: StateFlow<List<Expense>> = dao.observeAll()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())
fun add(title: String, rupees: Long, category: String) {
viewModelScope.launch { dao.insert(Expense(title = title, amountPaise = rupees * 100, category = category)) }
}
}Practice (0/3 done)
Which DAO return type gives the UI automatic updates when rows are inserted?
You added a column to an entity and shipped an update without a migration. What happens for existing users?
Build the data layer for a reading list: entity Book(id, title, author, pages, finished), a DAO with observeUnfinished(): Flow<List<Book>>, insert, markFinished(id) as an @Query UPDATE, and countFinished(): Flow<Int>. Create the database and show the unfinished books in a LazyColumn.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Networking with RetrofitSaving data and calling APIs · same module
- JSON models and handling errorsSaving data and calling APIs · same module
- Offline-first apps and DataStoreSaving data and calling APIs · same module
- Entity Framework Core: databases with C#C# and .NET · Web APIs with ASP.NET Core and EF Core
- Creating the database: schema, migrations and seed dataFull-Stack Development · Building the backend
- INSERT, UPDATE and DELETEBackend Development · Databases and SQL basics
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
What database engine does Room use underneath?
Answer: SQLite
Room is a layer over SQLite.
Which annotation marks the interface with query functions?
Answer: @Dao
DAO means Data Access Object.
Why is :category used in @Query instead of joining strings?
Answer: Room binds the value safely, preventing SQL injection
Bound parameters are safe and checked.
Which annotation processor setup does the current Room guide recommend?
Answer: KSP
KSP is faster; kapt is in maintenance mode.
What can Room return to get updates whenever a table changes?Related
Why should the Migrations folder be committed to Git?Related
Answer: It is the reviewed history of schema changes that every environment applies
Migrations are code that evolves the schema consistently.
What is a database migration?Related
Answer: A numbered file that changes the schema by one step, applied in order
Migrations are small, ordered schema changes kept in the repository, so every database can be built the same way.
From Creating the database: schema, migrations and seed data
What does RETURNING id do at the end of an INSERT?Related
Answer: Gives back the id of the new row
RETURNING sends back values from the changed rows, such as the new id.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 3 more topics in “Saving data and calling APIs” before its exam: Networking with Retrofit, JSON models and handling errors, Offline-first apps and DataStore.
- Module 8 exam: Saving data and calling APIs · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
- Then the next module, Permissions, Material 3 and polish. 50 topics left in the course before the final exam.