Library persistensi Room menyediakan lapisan abstraksi di atas SQLite untuk memungkinkan akses database yang lebih andal sekaligus memanfaatkan seluruh kapabilitas SQLite. Halaman ini berfokus pada penggunaan Room dalam project Kotlin Multiplatform (KMP). Untuk mengetahui informasi selengkapnya tentang penggunaan Room, lihat Menyimpan data di database lokal menggunakan Room atau contoh resmi kami.
Menyiapkan dependensi
Untuk menyiapkan Room di project KMP, tambahkan dependensi untuk artefak dalam
file build.gradle.kts untuk modul KMP Anda.
Tentukan dependensi dalam file libs.versions.toml:
[versions]
room3 = "3.1.0-alpha03"
sqlite = "2.7.1"
ksp = "<kotlinCompatibleKspVersion>"
[libraries]
androidx-sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "sqlite" }
androidx-room3-runtime = { module = "androidx.room3:room3-runtime", version.ref = "room3" }
androidx-room3-compiler = { module = "androidx.room3:room3-compiler", version.ref = "room3" }
# Optional SQLite Wrapper
androidx-room3-sqlite-wrapper = { module = "androidx.room3:room3-sqlite-wrapper", version.ref = "room3" }
[plugins]
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
androidx-room3 = { id = "androidx.room3", version.ref = "room3" }
Tambahkan Plugin Room Gradle untuk mengonfigurasi skema Room dan plugin KSP.
plugins {
alias(libs.plugins.ksp)
alias(libs.plugins.androidx.room3)
}
Tambahkan dependensi runtime Room dan library SQLite yang dipaketkan:
commonMain.dependencies {
implementation(libs.androidx.room3.runtime)
implementation(libs.androidx.sqlite.bundled)
}
// Optional when using Room SQLite Wrapper
androidMain.dependencies {
implementation(libs.androidx.room3.sqlite.wrapper)
}
Tambahkan dependensi KSP ke blok dependencies root. Anda harus menambahkan
semua target yang digunakan aplikasi Anda. Untuk informasi selengkapnya, lihat KSP dengan
Kotlin Multiplatform.
dependencies {
add("kspAndroid", libs.androidx.room3.compiler)
add("kspIosSimulatorArm64", libs.androidx.room3.compiler)
add("kspIosX64", libs.androidx.room3.compiler)
add("kspIosArm64", libs.androidx.room3.compiler)
// Add any other platform target you use in your project, for example kspDesktop
}
Tentukan direktori skema Room. Untuk informasi tambahan, lihat Menetapkan lokasi skema menggunakan Plugin Room Gradle.
room {
schemaDirectory("$projectDir/schemas")
}
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. Menempatkan
class ini di sumber umum akan memungkinkan class tersebut dibagikan di semua platform
target.
// 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("KotlinNoActualForExpect") expect object AppDatabaseConstructor : RoomDatabaseConstructor<AppDatabase> { override fun initialize(): AppDatabase }
Saat Anda mendeklarasikan objek expect dengan
antarmuka RoomDatabaseConstructor, compiler Room akan menghasilkan implementasi
actual. Android Studio mungkin akan menampilkan peringatan berikut, yang dapat Anda
abaikan dengan @Suppress("KotlinNoActualForExpect"):
Expected object 'AppDatabaseConstructor' has no actual declaration in module`
Selanjutnya, tentukan antarmuka DAO baru atau pindahkan antarmuka yang ada ke
commonMain:
// shared/src/commonMain/kotlin/TodoDao.kt @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>> }
Tentukan atau pindahkan entity Anda ke commonMain:
// shared/src/commonMain/kotlin/TodoEntity.kt @Entity data class TodoEntity( @PrimaryKey(autoGenerate = true) val id: Long = 0, val title: String, val content: String )
Membuat builder database khusus platform
Anda perlu menentukan builder database untuk membuat instance Room di setiap platform. Ini adalah satu-satunya bagian API yang harus Anda tentukan dalam set sumber khusus platform, karena perbedaan dalam API sistem file.
Android
Di Android, Anda biasanya mendapatkan lokasi database menggunakan
Context.getDatabasePath API. Untuk membuat instance database, tentukan
Context dan jalur database.
// shared/src/androidMain/kotlin/Database.android.kt fun getDatabaseBuilder(context: Context): RoomDatabase.Builder<AppDatabase> { val appContext = context.applicationContext val dbFile = appContext.getDatabasePath("my_room.db") return Room.databaseBuilder<AppDatabase>( context = appContext, name = dbFile.absolutePath ) }
iOS
Untuk membuat instance database di iOS, berikan jalur database menggunakan
NSFileManager, yang biasanya terletak di NSDocumentDirectory.
// shared/src/iosMain/kotlin/Database.ios.kt fun getDatabaseBuilder(): RoomDatabase.Builder<AppDatabase> { val dbFilePath = documentDirectory() + "/my_room.db" return Room.databaseBuilder<AppDatabase>( name = dbFilePath, ) } @OptIn(ExperimentalForeignApi::class) private fun documentDirectory(): String { val documentDirectory = NSFileManager.defaultManager.URLForDirectory( directory = NSDocumentDirectory, inDomain = NSUserDomainMask, appropriateForURL = null, create = false, error = null, ) return requireNotNull(documentDirectory?.path) }
Desktop JVM
Untuk membuat instance database, berikan jalur database menggunakan API Java atau Kotlin.
// shared/src/jvmMain/kotlin/Database.desktop.kt
fun getDatabaseBuilder(): RoomDatabase.Builder<AppDatabase> {
val dbFile = File(System.getProperty("java.io.tmpdir"), "my_room.db")
return Room.databaseBuilder<AppDatabase>(
name = dbFile.absolutePath,
)
}
Membuat 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 .setDriver(BundledSQLiteDriver()) .setQueryCoroutineContext(Dispatchers.IO) .build() }
Pilih driver SQLite
Cuplikan kode sebelumnya memanggil fungsi builder setDriver untuk menentukan driver SQLite yang harus digunakan database Room. Driver ini berbeda berdasarkan
platform target. Cuplikan kode sebelumnya menggunakan BundledSQLiteDriver.
Driver 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 API setDriver di
set sumber khusus platform yang menentukan driver khusus platform. Lihat Implementasi driver untuk deskripsi implementasi driver yang tersedia. Anda dapat menggunakan salah satu dari berikut ini:
AndroidSQLiteDriverdalamandroidMainNativeSQLiteDriverdalamiosMain
Untuk menggunakan NativeSQLiteDriver, Anda harus memberikan opsi penaut -lsqlite3 agar 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")
}
}
}
Menetapkan konteks coroutine
Objek RoomDatabase di Android dapat dikonfigurasi secara opsional dengan eksekutor aplikasi bersama menggunakan RoomDatabase.Builder.setQueryExecutor untuk melakukan operasi database.
Karena eksekutor tidak kompatibel dengan KMP, setQueryExecutor API Room tidak
tersedia di commonMain. Sebagai gantinya, objek RoomDatabase harus
dikonfigurasi dengan CoroutineContext, yang dapat Anda tetapkan menggunakan
RoomDatabase.Builder.setCoroutineContext. Jika Anda tidak menetapkan konteks, objek RoomDatabase akan ditetapkan secara default ke Dispatchers.IO.
Minifikasi dan obfuscation
Jika project diminimalkan atau di-obfuscate, Anda harus menyertakan aturan ProGuard berikut agar Room dapat menemukan implementasi yang dihasilkan dari definisi database:
-keep class * extends androidx.room3.RoomDatabase { <init>(); }
Bermigrasi ke Multiplatform Kotlin
Room awalnya dikembangkan sebagai library Android dan kemudian dimigrasikan ke KMP dengan fokus pada kompatibilitas API. Versi KMP Room agak berbeda antara platform dan dari versi khusus Android. Perbedaan ini dicantumkan dan dijelaskan sebagai berikut.
Bermigrasi dari Support SQLite ke Driver SQLite
Setiap penggunaan SupportSQLiteDatabase dan API lainnya di
androidx.sqlite.db perlu direfaktorisasi dengan SQLite Driver API,
karena API di androidx.sqlite.db hanya untuk Android (perhatikan
paket yang berbeda dengan paket KMP).
Untuk kompatibilitas mundur, dan selama RoomDatabase dikonfigurasi
dengan SupportSQLiteOpenHelper.Factory (misalnya, tidak ada SQLiteDriver yang
ditetapkan), Room akan berperilaku dalam 'mode kompatibilitas' dengan Support SQLite dan
SQLite Driver API berfungsi seperti yang diharapkan. Hal ini memungkinkan migrasi inkremental sehingga
Anda tidak perlu mengonversi semua penggunaan SQLite Dukungan ke Driver SQLite dalam
satu perubahan.
Menggunakan wrapper SQLite Room
Artefak androidx.room3:room3-sqlite-wrapper menyediakan API untuk menjembatani
SQLiteDriver dan SupportSQLiteDatabase selama migrasi.
Untuk mendapatkan SupportSQLiteDatabase dari RoomDatabase yang dikonfigurasi dengan
SQLiteDriver, gunakan fungsi ekstensi baru
RoomDatabase.getSupportWrapper. Wrapper kompatibilitas ini membantu mempertahankan
penggunaan SupportSQLiteDatabase yang ada, yang sering Anda peroleh dari
RoomDatabase.openHelper.writableDatabase, sekaligus mengadopsi SQLiteDriver,
terutama untuk codebase dengan penggunaan SupportSQLite API ekstensif yang ingin
menggunakan BundledSQLiteDriver.
Mengonversi subclass migrasi
Subclass Migrations perlu dimigrasikan ke pasangan driver SQLite:
Multiplatform Kotlin
Subclass migrasi
object Migration_1_2 : Migration(1, 2) { override suspend fun migrate(connection: SQLiteConnection) { // ... } }
Subclass spesifikasi migrasi otomatis
class AutoMigrationSpec_1_2 : AutoMigrationSpec { override suspend fun onPostMigrate(connection: SQLiteConnection) { // ... } }
Khusus Android
Subclass migrasi
object Migration_1_2 : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
// …
}
}
Subclass spesifikasi migrasi otomatis
class AutoMigrationSpec_1_2 : AutoMigrationSpec {
override fun onPostMigrate(db: SupportSQLiteDatabase) {
// …
}
}
Mengonversi callback database
Callback database harus dimigrasikan ke pasangan driver SQLite:
Multiplatform Kotlin
object MyRoomCallback : RoomDatabase.Callback() { override suspend fun onCreate(connection: SQLiteConnection) { // ... } override suspend fun onDestructiveMigration(connection: SQLiteConnection) { // ... } override suspend fun onOpen(connection: SQLiteConnection) { // ... } }
Khusus Android
object MyRoomCallback : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
// …
}
override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
// …
}
override fun onOpen(db: SupportSQLiteDatabase) {
// …
}
}
Mengonversi fungsi DAO @RawQuery
Fungsi yang dianotasi dengan @RawQuery yang dikompilasi untuk platform non-Android
harus mendeklarasikan parameter jenis RoomRawQuery, bukan
SupportSQLiteQuery.
Multiplatform Kotlin
Menentukan kueri mentah
@Dao interface TodoDao { @RawQuery suspend fun getTodos(query: RoomRawQuery): List<TodoEntity> }
RoomRawQuery kemudian dapat digunakan untuk membuat kueri saat runtime:
suspend fun AppDatabase.getTodosWithLowercaseTitle(title: String): List<TodoEntity> { val query = RoomRawQuery( sql = "SELECT * FROM TodoEntity WHERE title = ?", onBindStatement = { it.bindText(1, title.lowercase()) } ) return getDao().getTodos(query) }
Khusus Android
Menentukan kueri mentah
@Dao
interface TodoDao {
@RawQuery
suspend fun getTodos(query: SupportSQLiteQuery): List<TodoEntity>
}
SimpleSQLiteQuery kemudian dapat digunakan untuk membuat kueri saat runtime:
suspend fun AndroidOnlyDao.getTodosWithLowercaseTitle(title: String): List<TodoEntity> {
val query = SimpleSQLiteQuery(
query = "SELECT * FROM TodoEntity WHERE title = ?",
bindArgs = arrayOf(title.lowercase())
)
return getTodos(query)
}
Mengonversi fungsi DAO pemblokiran
Room memanfaatkan library kotlinx.coroutines asinkron yang kaya fitur
yang ditawarkan Kotlin untuk beberapa platform. Untuk fungsionalitas yang optimal, fungsi suspend
diterapkan untuk DAO yang dikompilasi dalam project KMP, kecuali untuk
DAO yang diimplementasikan di androidMain untuk mempertahankan kompatibilitas mundur dengan
codebase yang ada. Saat menggunakan Room untuk KMP, semua fungsi DAO yang dikompilasi untuk
platform non-Android harus berupa fungsi suspend.
Multiplatform Kotlin
Menangguhkan kueri
@Query("SELECT * FROM Todo") suspend fun getAllTodos(): List<Todo>
Menangguhkan transaksi
@Transaction suspend fun transaction() { // ... }
Khusus Android
Memblokir kueri
@Query("SELECT * FROM Todo")
fun getAllTodos(): List<Todo>
Memblokir transaksi
@Transaction
fun blockingTransaction() { … }
Mengonversi jenis reaktif ke Flow
Tidak semua fungsi DAO harus berupa fungsi penangguhan. Fungsi DAO yang menampilkan
jenis reaktif seperti LiveData atau Flowable RxJava tidak boleh dikonversi
menjadi fungsi penangguhan. Namun, beberapa jenis, seperti LiveData, tidak kompatibel
dengan KMP. Fungsi DAO dengan jenis nilai yang ditampilkan reaktif harus dimigrasikan ke
alur coroutine.
Multiplatform Kotlin
Jenis reaktif Flows
@Query("SELECT * FROM Todo") fun getTodosFlow(): Flow<List<Todo>>
Khusus Android
Jenis reaktif seperti LiveData atau Flowable RxJava
@Query("SELECT * FROM Todo")
fun getTodosLiveData(): LiveData<List<Todo>>
Mengonversi API transaksi
API transaksi database untuk Room KMP dapat membedakan antara transaksi penulisan (useWriterConnection) dan pembacaan (useReaderConnection).
Multiplatform Kotlin
val database: RoomDatabase = getRoomDatabase(builder) database.useWriterConnection { transactor -> transactor.immediateTransaction { // perform database operations in transaction. } }
Khusus Android
val database: RoomDatabase = …
database.withTransaction {
// perform database operations in transaction
}
Transaksi tulis
Gunakan transaksi tulis untuk memastikan bahwa beberapa kueri menulis data secara atomik,
sehingga pembaca dapat mengakses data secara konsisten. Anda dapat melakukannya menggunakan
useWriterConnection dengan salah satu dari tiga jenis transaksi:
immediateTransaction: Dalam mode Write-Ahead Logging (WAL) (default), jenis transaksi ini memperoleh kunci saat dimulai, tetapi pembaca dapat terus membaca. Ini adalah pilihan yang lebih disukai untuk sebagian besar kasus.deferredTransaction: Transaksi tidak akan memperoleh kunci hingga pernyataan tulis pertama. Gunakan jenis transaksi ini sebagai pengoptimalan jika Anda tidak yakin apakah operasi tulis akan diperlukan dalam transaksi. Misalnya, jika Anda memulai transaksi untuk menghapus lagu dari playlist yang hanya diberi nama playlist dan playlist tersebut tidak ada, operasi tulis (hapus) tidak diperlukan.exclusiveTransaction: Mode ini berperilaku sama denganimmediateTransactiondalam mode WAL. Dalam mode penjurnalan lainnya, mode ini mencegah koneksi database lain membaca database saat transaksi sedang berlangsung.
Transaksi baca
Gunakan transaksi baca untuk membaca dari database secara konsisten beberapa kali. Misalnya, saat Anda memiliki dua atau beberapa kueri terpisah dan tidak menggunakan klausa
JOIN. Hanya transaksi yang ditangguhkan yang diizinkan dalam koneksi pembaca. Upaya
untuk memulai transaksi langsung atau eksklusif dalam koneksi pembaca akan menampilkan
pengecualian, karena transaksi ini dianggap sebagai operasi 'tulis'.
val database: RoomDatabase = getRoomDatabase(builder) database.useReaderConnection { transactor -> transactor.deferredTransaction { // perform database operations in transaction. } }
Tidak tersedia di Multiplatform Kotlin
Beberapa API yang tersedia untuk Android tidak tersedia di Kotlin Multiplatform.
Callback kueri
API berikut untuk mengonfigurasi callback kueri tidak tersedia di common sehingga tidak tersedia di platform selain Android.
RoomDatabase.Builder.setQueryCallbackRoomDatabase.QueryCallback
Menutup database secara otomatis
API untuk mengaktifkan penutupan otomatis setelah waktu tunggu habis,
RoomDatabase.Builder.setAutoCloseTimeout, hanya tersedia di Android.
Database yang telah dikemas sebelumnya
API berikut untuk membuat RoomDatabase menggunakan database yang ada (database dalam bentuk paket) tidak tersedia secara umum dan oleh karena itu tidak tersedia di platform selain Android:
RoomDatabase.Builder.createFromAssetRoomDatabase.Builder.createFromFileRoomDatabase.Builder.createFromInputStreamRoomDatabase.PrepackagedDatabaseCallback
Invalidasi multi-instance
API untuk mengaktifkan invalidasi multi-instance,
RoomDatabase.Builder.enableMultiInstanceInvalidation, hanya tersedia di
Android dan tidak tersedia di platform umum atau platform lainnya.
Direkomendasikan untuk Anda
- Catatan: teks link ditampilkan saat JavaScript nonaktif
- Codelab Memigrasikan aplikasi yang ada ke Room KMP
- Memulai Codelab KMP
- Menyimpan data di database lokal dengan Room