Room (Kotlin Multiplatform)

Room 持續性程式庫透過 SQLite 提供抽象層, 以便更穩健地存取資料庫,同時充分發揮 SQLite 的效用。這個 頁面著重於在 Kotlin Multiplatform (KMP) 專案中使用 Room。如要 有關使用 Room 的資訊,請參閱「使用 Room 將資料儲存在本機資料庫」一文 或我們的官方範例

設定依附元件

目前支援 KMP 的 Room 版本為 2.7.0-alpha01 以上版本。

如要在 KMP 專案中設定 Room,請在存放區中新增構件的依附元件 模組的 build.gradle.kts 檔案:

  • androidx.room:room-gradle-plugin:用於設定 Room 結構定義的 Gradle 外掛程式
  • androidx.room:room-compiler:用來產生程式碼的 KSP 處理器
  • androidx.room:room-runtime - 程式庫的執行階段部分
  • androidx.sqlite:sqlite-bundled - (選用) 隨附的 SQLite 程式庫

此外,您必須設定 Room 的 SQLite 驅動程式。這些因素各異 和目標平台互動詳情請見 駕駛人實作 以取得可用的驅動程式實作說明。

如需其他設定資訊,請參閱以下資源:

,瞭解如何調查及移除這項存取權。

定義資料庫類別

您需要建立含有 @Database 註解與 DAO 的資料庫類別 共用 KMP 模組的通用來源集中內的實體和實體。位置 共用來源中的這些類別,讓所有目標都能共用這些類別 平台。

使用介面宣告 expect 物件時 RoomDatabaseConstructor,Room 編譯器會產生 actual 。Android Studio 可能會發出警示 "Expected object 'AppDatabaseConstructor' has no actual declaration in module";可以使用 @Suppress("NO_ACTUAL_FOR_EXPECT") 隱藏警告

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

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

// The Room compiler generates the `actual` implementations.
@Suppress("NO_ACTUAL_FOR_EXPECT")
expect object AppDatabaseConstructor : RoomDatabaseConstructor<AppDatabase>

@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
)

請注意,您可以視需要使用實際 / 預期宣告 建立平台專屬的 Room 實作項目。舉例來說,您可以將 使用 expect 定義平台專屬 DAO,然後 指定 actual 定義,並在平台專屬查詢中提供額外查詢 來源集。

建立資料庫建構工具

您需要定義資料庫建構工具,在每個平台上將 Room 執行個體化。這個 是 API 只需要納入平台專用來源的唯一部分 原因是檔案系統 API 的差異。以 Android 為例 資料庫位置通常是透過 Context.getDatabasePath() API 的資料庫位置;如果是 iOS 裝置,資料庫位置則為 透過 NSFileManager 取得。

Android

如要建立資料庫執行個體,請搭配資料庫指定「Context」 路徑。

// 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

如要建立資料庫執行個體,請使用 NSFileManager,通常位於 NSDocumentDirectory

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

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

private fun documentDirectory(): String {
  val documentDirectory = NSFileManager.defaultManager.URLForDirectory(
    directory = NSDocumentDirectory,
    inDomain = NSUserDomainMask,
    appropriateForURL = null,
    create = false,
    error = null,
  )
  return requireNotNull(documentDirectory?.path)
}

JVM (電腦)

如要建立資料庫執行個體,請使用 Java 或 Kotlin 提供資料庫路徑 相互整合

// shared/src/jvmMain/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,
    )
}

資料庫例項化

從任一平台特定平台取得 RoomDatabase.Builder 後 建構函式建立而成,您可以在通用程式碼中設定 Room 資料庫的其餘部分 以及實際的資料庫例項化作業

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

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

選取 SQLiteDriver

先前的程式碼片段使用 BundledSQLiteDriver。這是 包括從來源編譯的 SQLite 驅動程式 所有平台上的 SQLite 版本都具有一致性和最新版本。如果發生以下情況: 要使用 OS 提供的 SQLite,請在平台專用時使用 setDriver API 指定平台專用驅動程式的來源集。如果是 Android,您可以使用 AndroidSQLiteDriver,如果是 iOS 裝置,則可以使用 NativeSQLiteDriver。目的地: 使用 NativeSQLiteDriver,您必須提供連接器選項,iOS 應用程式會以系統 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")
        }
    }
}

相異處

Room 最初是開發為 Android 程式庫,後來遷移至 著重 API 相容性的 KMP。Room 的 KMP 版本與眾不同 以及 Android 專屬版本之間的互動兩者的差異 列出和描述。

封鎖 DAO 函式

使用 Room for KMP 時,針對非 Android 平台編譯的所有 DAO 函式 需為 suspend 函式,但回應式傳回類型除外,例如 使用 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 具備功能豐富的非同步 kotlinx.coroutines 程式庫的優點 許多平台都能使用 Kotlin 提供的多種功能為獲得最佳功能,請suspend 函式強制執行於 KMP 專案中編譯的 DAO,但 Android 專屬的 DAO,以維持與現有物件的回溯相容性 程式碼集

與 KMP 的功能差異

本節說明 KMP 和 Android 平台的功能差異 每個版本都會發生錯誤

@RawQuery DAO 函式

加上 @RawQuery 註解的函式,專為非 Android 平台編譯而成 您需要宣告 RoomRawQuery 類型的參數 SupportSQLiteQuery

@Dao
interface TodoDao {
  @RawQuery
  suspend fun getTodos(query RoomRawQuery): List<TodoEntity>
}

接著,您就能使用 RoomRawQuery 在執行階段建立查詢:

suspend fun getTodosWithLowercaseTitle(title: String): List<TodoEntity> {
  val query = RoomRawQuery(
    sql = "SELECT * FROM TodoEntity WHERE title = ?"
    onBindStatement = {
      it.bindText(1, title.lowercase())
    }
  )
  return todosDao.getTodos(query)
}

查詢回呼

下列 API 無法用於設定查詢回呼 因此不適用於 Android 以外的平台

  • RoomDatabase.Builder.setQueryCallback
  • RoomDatabase.QueryCallback

我們會在日後的 Room 版本中,新增查詢回呼的支援功能。

這個 API 用於設定含有查詢回呼的 RoomDatabase RoomDatabase.Builder.setQueryCallback 與回呼介面 RoomDatabase.QueryCallback 無法使用,因此不適用 也就是 Android 以外的平台

自動關閉資料庫

這個 API 可用來啟用在逾時後自動關閉的功能 RoomDatabase.Builder.setAutoCloseTimeout,僅適用於 Android 裝置,而且 其他平台不適用

預先封裝資料庫

下列 API 可RoomDatabase使用現有資料庫 (即 預先封裝的資料庫) 並不常見,因此也無法用於 也就是 Android 以外的其他平台這些 API 如下:

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

我們會在日後的 房間。

多執行個體撤銷

用來啟用多執行個體撤銷的 API, 「RoomDatabase.Builder.enableMultiInstanceInvalidation」僅適用於 Android 裝置,不適用於常見平台或其他平台。