Android Room
Room is the default Android abstraction for non-trivial structured local data. It gives you entities, DAOs, compile-time SQL checks, migrations, and test helpers over SQLite.
@Entity(tableName = "transactions")
data class TransactionEntity(
@PrimaryKey val id: String,
val occurredAt: Instant,
val amountCents: Long,
val currency: String,
val merchant: String?,
val note: String?
)
@Dao
interface TransactionDao {
@Query("SELECT * FROM transactions ORDER BY occurredAt DESC")
fun observeAll(): Flow<List<TransactionEntity>>
@Insert(onConflict = OnConflictStrategy.ABORT)
suspend fun insert(transaction: TransactionEntity)
}
Room Pieces
| Piece | Purpose |
|---|---|
| Entity | table shape and indexes |
| DAO | SQL boundary for reads and writes |
| Database class | version, entities, type converters, migrations |
| Migration | explicit schema/data transition between versions |
| Schema export | JSON history used by migration tests and reviews |
Migration Rule
For user-owned data, do not use destructive migration as a normal upgrade path.
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE transactions ADD COLUMN note TEXT")
}
}
fallbackToDestructiveMigration() is useful in prototypes and disposable caches. It is dangerous for user-owned data because it can drop tables when Room cannot find a migration path.
Testing
Room migration tests should create an old database, insert real rows, migrate to the latest version, validate the schema, and assert the rows survived.
@Test
fun migrate1To2_preservesTransactions() {
helper.createDatabase(TEST_DB, 1).apply {
execSQL("INSERT INTO transactions (id, occurredAt, amountCents, currency) VALUES ('t1', '2026-01-01T10:00:00Z', 5000, 'KES')")
close()
}
val db = helper.runMigrationsAndValidate(TEST_DB, 2, true, MIGRATION_1_2)
db.query("SELECT amountCents FROM transactions WHERE id = 't1'").use { cursor ->
assertThat(cursor.moveToFirst()).isTrue()
assertThat(cursor.getLong(0)).isEqualTo(5000L)
}
}
Gotcha: Host-side SQLite may not match device SQLite. Use instrumented tests for migration behavior that must match real devices.