Room (Multiplatform Kotlin)

Library persistensi Room menyediakan lapisan abstraksi pada SQLite untuk memungkinkan akses database yang lebih stabil sambil memanfaatkan kemampuan penuh SQLite. Halaman ini berfokus pada penggunaan Room dalam project Multiplatform Kotlin (KMP). Untuk mengetahui informasi selengkapnya tentang cara menggunakan Room, lihat Menyimpan data di database lokal menggunakan Room atau contoh resmi kami.

Menyiapkan dependensi

Versi Room saat ini yang mendukung KMP adalah 2.7.0-alpha01 atau yang lebih tinggi.

Untuk menyiapkan Room di project KMP, tambahkan dependensi untuk artefak dalam file build.gradle.kts untuk modul Anda:

  • androidx.room:room-gradle-plugin - Plugin Gradle untuk mengonfigurasi skema Room
  • androidx.room:room-compiler - Prosesor KSP yang menghasilkan kode
  • androidx.room:room-runtime - Bagian runtime library
  • androidx.sqlite:sqlite-bundled - (Opsional) Library SQLite yang dipaketkan

Selain itu, Anda perlu mengonfigurasi driver SQLite Room. Driver ini berbeda berdasarkan platform target. Lihat Implementasi driver untuk mengetahui deskripsi implementasi driver yang tersedia.

Untuk informasi penyiapan tambahan, lihat referensi berikut:

Menentukan class database

Anda perlu membuat class database yang dianotasi dengan @Database beserta DAO dan entity di dalam set sumber umum modul KMP bersama Anda. Dengan menempatkan class ini di sumber umum, class tersebut dapat dibagikan di semua platform target.

Saat Anda mendeklarasikan objek expect dengan antarmuka RoomDatabaseConstructor, compiler Room akan menghasilkan implementasi actual. Android Studio mungkin mengeluarkan peringatan "Expected object 'AppDatabaseConstructor' has no actual declaration in module"; Anda dapat menyembunyikan peringatan dengan @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> {
    override fun initialize(): 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
)

Perhatikan bahwa Anda dapat menggunakan deklarasi actual / expect secara opsional untuk membuat implementasi Room khusus platform. Misalnya, Anda dapat menambahkan DAO khusus platform yang ditentukan dalam kode umum menggunakan expect, lalu menentukan definisi actual dengan kueri tambahan dalam set sumber khusus platform.

Membuat builder database

Anda perlu menentukan builder database untuk membuat instance Room di setiap platform. Ini adalah satu-satunya bagian API yang harus berada dalam set sumber khusus platform karena perbedaan dalam API sistem file. Misalnya, di Android, lokasi database biasanya diperoleh melalui API Context.getDatabasePath(), sedangkan untuk iOS, lokasi database diperoleh menggunakan NSFileManager.

Android

Untuk membuat instance database, tentukan Konteks beserta jalur database.

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

Untuk membuat instance database, berikan jalur database menggunakan NSFileManager, yang biasanya terletak di 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 (Desktop)

Untuk membuat instance database, berikan jalur database menggunakan API Java atau 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,
    )
}

Minifikasi dan obfuscation

Jika project diminifikasi atau di-obfuscate, aturan proguard berikut harus disertakan agar Room dapat menemukan implementasi definisi database yang dihasilkan:

-keep class * extends androidx.room.RoomDatabase { <init>(); }

Pembuatan instance database

Setelah mendapatkan RoomDatabase.Builder dari salah satu konstruktor khusus platform, Anda dapat mengonfigurasi database Room lainnya dalam kode umum bersama dengan pembuatan instance database yang sebenarnya.

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

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

Memilih SQLiteDriver

Cuplikan kode sebelumnya menggunakan BundledSQLiteDriver. Ini adalah driver yang direkomendasikan yang menyertakan SQLite yang dikompilasi dari sumber, yang menyediakan versi SQLite yang paling konsisten dan terbaru di semua platform. Jika Anda ingin menggunakan SQLite yang disediakan OS, gunakan setDriver API dalam set sumber khusus platform yang menentukan driver khusus platform. Untuk Android, Anda dapat menggunakan AndroidSQLiteDriver, sedangkan untuk iOS, Anda dapat menggunakan NativeSQLiteDriver. Untuk menggunakan NativeSQLiteDriver, Anda perlu memberikan opsi penaut sehingga aplikasi iOS ditautkan secara dinamis dengan SQLite sistem.

// 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")
        }
    }
}

Perbedaan

Room awalnya dikembangkan sebagai library Android dan kemudian dimigrasikan ke KMP dengan fokus pada kompatibilitas API. Versi KMP Room sedikit berbeda di antara platform dan dari versi khusus Android. Perbedaan ini tercantum dan dijelaskan sebagai berikut.

Memblokir fungsi DAO

Saat menggunakan Room untuk KMP, semua fungsi DAO yang dikompilasi untuk platform non-Android harus berupa fungsi suspend, kecuali jenis nilai yang ditampilkan reaktif, seperti 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 mendapatkan manfaat dari library kotlinx.coroutines asinkron yang kaya fitur yang ditawarkan Kotlin untuk beberapa platform. Untuk fungsi yang optimal, fungsi suspend diberlakukan untuk DAO yang dikompilasi dalam project KMP, dengan pengecualian DAO khusus Android untuk mempertahankan kompatibilitas mundur dengan codebase yang ada.

Perbedaan fitur dengan KMP

Bagian ini menjelaskan perbedaan fitur antara Room versi platform Android dan KMP.

Fungsi DAO @RawQuery

Fungsi yang dianotasi dengan @RawQuery yang dikompilasi untuk platform non-Android harus mendeklarasikan parameter jenis RoomRawQuery, bukan SupportSQLiteQuery.

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

RoomRawQuery kemudian dapat digunakan untuk membuat kueri saat runtime:

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

Callback Kueri

API berikut untuk mengonfigurasi callback kueri tidak tersedia secara umum sehingga tidak tersedia di platform selain Android.

  • RoomDatabase.Builder.setQueryCallback
  • RoomDatabase.QueryCallback

Kami bermaksud menambahkan dukungan untuk callback kueri di versi Room mendatang.

API untuk mengonfigurasi RoomDatabase dengan callback kueri RoomDatabase.Builder.setQueryCallback beserta antarmuka callback RoomDatabase.QueryCallback tidak tersedia secara umum sehingga tidak tersedia di platform lain selain Android.

Database Penutupan Otomatis

API untuk mengaktifkan penutupan otomatis setelah waktu tunggu habis, RoomDatabase.Builder.setAutoCloseTimeout, hanya tersedia di Android dan tidak tersedia di platform lain.

Database Prapaket

API berikut untuk membuat RoomDatabase menggunakan database yang ada (yaitu database dalam bentuk paket) tidak tersedia secara umum sehingga tidak tersedia di platform lain selain Android. API ini adalah:

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

Kami bermaksud menambahkan dukungan untuk database yang dikemas sebelumnya dalam versi Room mendatang.

Pembatalan Validasi Multi-Instance

API untuk mengaktifkan pembatalan validasi multi-instance, RoomDatabase.Builder.enableMultiInstanceInvalidation hanya tersedia di Android dan tidak tersedia di platform umum atau platform lainnya.