Room (Kotlin Multiplatform)

The Room persistence library provides an abstraction layer over SQLite to allow for more robust database access while harnessing the full power of SQLite. This page focuses on using Room in Kotlin Multiplatform (KMP) projects. For more information on using Room, see Save data in a local database using Room or our official samples.

Setting up dependencies

The current version of Room that supports KMP is 2.7.0-alpha01 or higher.

To setup Room in your KMP project, add the dependencies for the artifacts in the build.gradle.kts file for your module:

  • androidx.room:room-gradle-plugin - The Gradle Plugin to configure Room schemas
  • androidx.room:room-compiler - The KSP processor that generates code
  • androidx.room:room-runtime - The runtime part of the library
  • androidx.sqlite:sqlite-bundled - (Optional) The bundled SQLite library

Additionally you need to configure Room's SQLite driver. These drivers differ based on the target platform. See Driver implementations for descriptions of available driver implementations.

For additional setup information, see the following:

Defining the database classes

You need to create a database class annotated with @Database along with DAOs and entities inside the common source set of your shared KMP module. Placing these classes in common sources will allow them to be shared across all target platforms.

// shared/src/commonMain/kotlin/Database.kt

@Database(entities = [TodoEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
  abstract fun getDao(): TodoDao
}

@Dao
interface TodoDao {
  @Insert
  suspend fun insert(item: TodoEntity)

  @Query("SELECT count(*) FROM TodoEntity")
  suspend fun count(): Int

  @Query("SELECT * FROM TodoEntity")
  fun getAllAsFlow(): Flow<List<TodoEntity>>
}

@Entity
data class TodoEntity(
  @PrimaryKey(autoGenerate = true) val id: Long = 0,
  val title: String,
  val content: String
)

Note that you can use actual / expect declarations to create platform-specific Room implementations. For example, you can add a platform-specific DAO that is defined in common code using expect and then specify the actual definitions with additional queries in platform-specific source sets.

Creating the database builder

You need to define a database builder to instantiate Room on each platform. This is the only part of the API that is required to be in platform-specific source sets due to the differences in file system APIs. For example, in Android, database location is usually obtained through the Context.getDatabasePath() API, while for iOS, the database location is obtained using NSHomeDirectory.

Android

To create the database instance, specify a Context along with the database path. You don't need to specify a database factory.

// shared/src/androidMain/kotlin/Database.kt

fun getDatabaseBuilder(ctx: Context): RoomDatabase.Builder<AppDatabase> {
    val appContext = ctx.applicationContext
    val dbFile = appContext.getDatabasePath("my_room.db")
    return Room.databaseBuilder<AppDatabase>(
        context = appContext,
        name = dbFile.absolutePath
    )
}

iOS

To create the database instance, provide a database factory along with the database path. The database factory is a lambda function that invokes a generated extension function whose name is instantiateImpl with a receiver of type KClass<T>, where T is the type of the @Database annotated class.

// shared/src/iosMain/kotlin/Database.kt

fun getDatabaseBuilder(): RoomDatabase.Builder<AppDatabase> {
    val dbFilePath = NSHomeDirectory() + "/my_room.db"
    return Room.databaseBuilder<AppDatabase>(
        name = dbFilePath,
        factory =  { AppDatabase::class.instantiateImpl() }
    )
}

JVM (Desktop)

To create the database instance, specify only the database path. You don't need to provide a database factory.

// shared/src/commonMain/kotlin/Database.kt

fun getDatabaseBuilder(): RoomDatabase.Builder<AppDatabase> {
    val dbFile = File(System.getProperty("java.io.tmpdir"), "my_room.db")
    return Room.databaseBuilder<AppDatabase>(
        name = dbFile.absolutePath,
    )
}

Database instantiation

Once you obtain the RoomDatabase.Builder from one of the platform-specific constructors, you can configure the rest of the Room database in common code along with the actual database instantiation.

// shared/src/commonMain/kotlin/Database.kt

fun getRoomDatabase(
    builder: RoomDatabase.Builder<AppDatabase>
): AppDatabase {
    return builder
        .addMigrations(MIGRATIONS)
        .fallbackToDestructiveMigrationOnDowngrade()
        .setDriver(BundledSQLiteDriver())
        .setQueryCoroutineContext(Dispatchers.IO)
        .build()
}

Selecting a SQLiteDriver

The previous code snippets use the BundledSQLiteDriver. This is the recommended driver that includes SQLite compiled from source, which provides the most consistent and up-to-date version of SQLite across all platforms. If you wish to use the OS-provided SQLite, use the setDriver API in platform-specific source sets that specify a platform-specific driver. For Android, you can use AndroidSQLiteDriver, while for iOS you can use the NativeSQLiteDriver. To use NativeSQLiteDriver, you need to provide a linker option so that the iOS app dynamically links with the system SQLite.

// shared/build.gradle.kts

kotlin {
    listOf(
        iosX64(),
        iosArm64(),
        iosSimulatorArm64()
    ).forEach { iosTarget ->
        iosTarget.binaries.framework {
            baseName = "TodoApp"
            isStatic = true
            // Required when using NativeSQLiteDriver
            linkerOpts.add("-lsqlite3")
        }
    }
}

Differences

Room was originally developed as an Android library and was later migrated to KMP with a focus on API compatibility. The KMP version of Room differs somewhat between platforms and from the Android-specific version. These differences are listed and described as follows.

Blocking DAO functions

When using Room for KMP, all DAO functions compiled for non-Android platforms need to be suspend functions with the exception of reactive return types, such as Flow.

// shared/src/commonMain/kotlin/MultiplatformDao.kt

@Dao
interface MultiplatformDao {
    // ERROR: Blocking function not valid for non-Android targets
    @Query("SELECT * FROM Entity")
    fun blockingQuery(): List<Entity>

    // OK
    @Query("SELECT * FROM Entity")
    suspend fun query(): List<Entity>

    // OK
    @Query("SELECT * FROM Entity")
    fun queryFlow(): Flow<List<Entity>>

    // ERROR: Blocking function not valid for non-Android targets
    @Transaction
    fun blockingTransaction() { // … }

    // OK
    @Transaction
    suspend fun transaction() { // … }
}

Room benefits from the feature-rich asynchronous kotlinx.coroutines library that Kotlin offers for multiple platforms. For optimal functionality, suspend functions are enforced for DAOs compiled in a KMP project, with the exception of Android specific DAOs to maintain backwards compatibility with the existing codebase.

Feature differences with KMP

This section describes how features differ between KMP and Android platform versions of Room.

@RawQuery DAO functions

Functions annotated with @RawQuery that are compiled for non-Android platforms will produce an error. We intend to add support for @RawQuery in a future version of Room.

Query Callback

The following APIs for configuring query callbacks are not available in common and are thus unavailable in platforms other than Android.

  • RoomDatabase.Builder.setQueryCallback
  • RoomDatabase.QueryCallback

We intend to add support for query callback in a future version of Room.

The API to configure a RoomDatabase with a query callback RoomDatabase.Builder.setQueryCallback along with the callback interface RoomDatabase.QueryCallback are not available in common and thus not available in other platforms other than Android.

Auto Closing Database

The API to enable auto-closing after a timeout, RoomDatabase.Builder.setAutoCloseTimeout, is only available on Android and is not available in other platforms.

Pre-package Database

The following APIs to create a RoomDatabase using an existing database (i.e. a pre-packaged database) are not available in common and are thus not available in other platforms other than Android. These APIs are:

  • RoomDatabase.Builder.createFromAsset
  • RoomDatabase.Builder.createFromFile
  • RoomDatabase.Builder.createFromInputStream
  • RoomDatabase.PrepackagedDatabaseCallback

We intend to add support for pre-packaged databases in a future version of Room.

Multi-Instance Invalidation

The API to enable multi-instance invalidation, RoomDatabase.Builder.enableMultiInstanceInvalidation is only available on Android and is not available in common or other platforms.