Android Room Local-First
This is the minimum shape for an Android local-first store: Room tables, explicit migrations, export hooks, and no destructive fallback for user-owned data.
Database
@Database(
entities = [TransactionEntity::class, PendingMutationEntity::class],
version = 2,
exportSchema = true
)
@TypeConverters(InstantConverter::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun transactions(): TransactionDao
abstract fun pendingMutations(): PendingMutationDao
}
Builder
fun buildDatabase(context: Context): AppDatabase {
return Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"app.db"
)
.addMigrations(MIGRATION_1_2)
.build()
}
Do not add fallbackToDestructiveMigration() for user-owned data.
Export Hook
suspend fun exportTransactions(db: AppDatabase, sink: BufferedSink) {
sink.writeUtf8("id,occurred_at,amount_cents,currency,merchant\n")
db.transactions().getAllForExport().forEach { row ->
sink.writeUtf8("${row.id},${row.occurredAt},${row.amountCents},${row.currency},${row.merchant.orEmpty()}\n")
}
}
For privacy-first finance apps, CSV export is table stakes. Full backup can come later, but export should ship early.
Release Gate
- migration test from every supported version to current
- DAO tests on device/instrumented SQLite
- CSV export test with thousands of rows
- restore/import smoke test if backup exists
- app lock and key management reviewed for private data
- offline launch works with no network