Skip to content

Room

Databases & CachingDatabase/ORMKotlin

What it is

Room is Android's persistence library, providing a typed abstraction over SQLite with compile-time query verification and Flow observation.

Entities describe tables, DAOs declare queries, and a Database class ties them together. Queries returning Flow emit again whenever the underlying data changes.

Installation

implementation("androidx.room:room-runtime:2.6.1")

Getting started

The smallest useful thing you can do with it, and what each part means.

Entity, DAO and reactive queries
@Entity(tableName = "books", indices = [Index("year")])
data class BookEntity(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    val title: String,
    val year: Int,
)

@Dao
interface BookDao {
    // Verified against the schema at compile time.
    @Query("SELECT * FROM books WHERE year >= :minYear ORDER BY year DESC")
    fun observeSince(minYear: Int): Flow<List<BookEntity>>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun upsert(book: BookEntity)

    @Query("DELETE FROM books WHERE id = :id")
    suspend fun delete(id: Int)
}
A Flow return type makes the query observable — insert a row and every collector re-emits. Suspend functions keep writes off the main thread automatically.

Advanced usage

Where the library earns its place over a simpler alternative.

Migrations and relations
val MIGRATION_1_2 = object : Migration(1, 2) {
    override fun migrate(db: SupportSQLiteDatabase) {
        db.execSQL("ALTER TABLE books ADD COLUMN isbn TEXT")
    }
}

Room.databaseBuilder(context, AppDatabase::class.java, "app.db")
    .addMigrations(MIGRATION_1_2)
    // Never ship fallbackToDestructiveMigration — it deletes user data.
    .build()

data class BookWithAuthor(
    @Embedded val book: BookEntity,
    @Relation(parentColumn = "authorId", entityColumn = "id")
    val author: AuthorEntity,
)

@Transaction
@Query("SELECT * FROM books")
fun observeWithAuthors(): Flow<List<BookWithAuthor>>
@Transaction on a relation query matters: Room runs several statements to assemble the objects, and without it another write can interleave and produce an inconsistent result.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

Room cannot verify the data integrity
The schema changed without a migration. Add a Migration for the version bump.
Cannot access database on the main thread
A synchronous DAO method was called on the UI thread. Make it suspend or return Flow.

Best practices

  • Write real migrations; fallbackToDestructiveMigration silently wipes user data.
  • Export the schema JSON and commit it so migrations can be tested.
  • Return Flow for anything the UI observes, and suspend for one-off reads and writes.
  • Add @Transaction to queries that assemble relations.

Background

Why it exists, and what it was reacting to.

Room replaced hand-written SQLiteOpenHelper code. Its defining feature is that it parses every @Query at compile time against the schema, so a mistyped column fails the build.