Databases

August 23, 2026 ยท View on GitHub

Declare

@Database(
    version = 1,
    foreignKeyConstraintsEnforced = true,
    tables = [User::class, Post::class],
    views = [AuthorView::class],
    queries = [UserNameQuery::class],
    migrations = [AddEmailMigration::class],
)
abstract class AppDatabase : DBFlowDatabase<AppDatabase>() {
    abstract val userAdapter: ModelAdapter<User>
    abstract val postAdapter: ModelAdapter<Post>
    abstract val authorViewAdapter: ViewAdapter<AuthorView>
    abstract val userNameQueryAdapter: QueryAdapter<UserNameQuery>
}

The generated type is AppDatabase_Database. Its companion implements DBCreator<AppDatabase>.

Default file name is the class name plus .db (AppDatabase.db). Override in settings.

A table belongs to one database. List it on @Database (preferred) or set @Table(database = AppDatabase::class).

Open

Call createDB โ€” the compiler plugin rewrites it to the generated factory. Works from commonMain; the Gradle plugin generates sources before compilation.

// Android
val db = createDB<AppDatabase>(context) {
    copy(name = "App", inMemory = false)
}

// JVM / Native
val db = createDB<AppDatabase> {
    copy(name = "App")
}

// Explicit platform settings (all targets)
val db = createDB<AppDatabase>(DBPlatformSettings()) {
    copy(name = "App")
}

createDB registers generated adapters (User.Companion as ModelAdapter<User>, and the same for views and query models) so KClass lookups such as select from User::class work after open. select from User uses the table companion directly and does not need a lookup.

DBSettings fields you typically copy:

FieldDefaultPurpose
namedatabase class nameFile stem (must match [A-Za-z_$][A-Za-z0-9_$]*)
inMemoryfalseTests / short-lived DBs
databaseExtensionName".db"File suffix
journalModeAutomaticWAL on capable devices
openHelperCreatorplatform SQLiteSQLCipher or a fake in tests
databaseCallbacknullOpen / upgrade hooks
throwExceptionsOnCreatetrueFail fast on create errors

The first access to writableDatabase opens the file and runs migrations.

db.use {
    writableDatabase // force open
}

close() stops the dispatcher and closes the connection. destroy() also deletes the file.

Transactions

db.writableTransaction {
    userAdapter.save(user)
}

db.readableTransaction {
    userAdapter.select().list()
}

Both hop to transactionCoroutineDispatcher (single-thread executor by default). Nested work on the same database stays on that dispatcher.

Callbacks (async Transaction success / error) use callbackDispatcher (Dispatchers.Main on Android and JVM).

Platforms

TargetOpen helperNotes
AndroidAndroidSQLiteOpenHelpercreate(context) needs Context
JVMJDBC + sqlite-jdbccreate { }
NativesqliterLink -lsqlite3

Multiple databases

Declare a second @Database and create it the same way. Do not share @Table types across databases.