Library persistensi Room memberikan 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 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]
room = "2.8.4"
sqlite = "2.6.2"
ksp = "<kotlinCompatibleKspVersion>"
[libraries]
androidx-sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "sqlite" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
# Optional SQLite Wrapper available in version 2.8.0 and higher
androidx-room-sqlite-wrapper = { module = "androidx.room:room-sqlite-wrapper", version.ref = "room" }
[plugins]
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
androidx-room = { id = "androidx.room", version.ref = "room" }
Tambahkan Plugin Gradle Room untuk mengonfigurasi skema Room dan plugin KSP.
plugins {
alias(libs.plugins.ksp)
alias(libs.plugins.androidx.room)
}
Tambahkan dependensi runtime Room dan library SQLite yang digabungkan:
commonMain.dependencies {
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.sqlite.bundled)
}
// Optional when using Room SQLite Wrapper
androidMain.dependencies {
implementation(libs.androidx.room.sqlite.wrapper)
}
Tambahkan dependensi KSP ke blok dependencies root. Perhatikan bahwa Anda harus menambahkan semua target yang digunakan aplikasi Anda. Untuk mengetahui informasi selengkapnya, lihat KSP dengan
Multiplatform Kotlin.
dependencies {
add("kspAndroid", libs.androidx.room.compiler)
add("kspIosSimulatorArm64", libs.androidx.room.compiler)
add("kspIosX64", libs.androidx.room.compiler)
add("kspIosArm64", libs.androidx.room.compiler)
// Add any other platform target you use in your project, for example kspDesktop
}
Tentukan direktori skema Room. Untuk mengetahui informasi tambahan, lihat Menetapkan lokasi skema menggunakan Plugin Gradle Room.
room {
schemaDirectory("$projectDir/schemas")
}
Menentukan class database
Anda harus membuat class database yang diberi anotasi dengan @Database bersama dengan 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 mengeluarkan peringatan berikut, yang dapat Anda
nonaktifkan 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 harus 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.
Android
Di Android, lokasi database biasanya diperoleh melalui
Context.getDatabasePath() API. Untuk membuat instance database, tentukan a
Context bersama dengan 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,
)
}
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 Java atau Kotlin API.
// 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()
}
Memilih 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.
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 ingin menggunakan SQLite yang disediakan OS, gunakan setDriver API dalam set sumber khusus platform yang menentukan driver khusus platform. Lihat
Implementasi driver untuk mengetahui deskripsi implementasi driver yang tersedia. Anda dapat menggunakan salah satu hal berikut:
AndroidSQLiteDriverdiandroidMainNativeSQLiteDriverdiiosMain
Untuk menggunakan NativeSQLiteDriver, Anda harus memberikan opsi linker -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 (Opsional)
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 ditetapkan menggunakan
RoomDatabase.Builder.setCoroutineContext(). Jika tidak ada konteks yang ditetapkan, objek RoomDatabase akan menggunakan Dispatchers.IO secara default.
Minifikasi dan pengaburan
Jika project diminifikasi atau dikaburkan, Anda harus menyertakan aturan ProGuard berikut agar Room dapat menemukan implementasi definisi database yang dihasilkan:
-keep class * extends androidx.room.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 sedikit berbeda antara platform dan dari versi khusus Android. Perbedaan ini tercantum dan dijelaskan sebagai berikut.
Bermigrasi dari Support SQLite ke Driver SQLite
Setiap penggunaan SupportSQLiteDatabase dan API lainnya di
androidx.sqlite.db harus difaktorkan ulang dengan SQLite Driver API,
karena API di androidx.sqlite.db hanya untuk Android (perhatikan
paket yang berbeda dari paket KMP).
Untuk kompatibilitas mundur, dan selama RoomDatabase dikonfigurasi dengan SupportSQLiteOpenHelper.Factory (misalnya, tidak ada SQLiteDriver yang ditetapkan), Room akan berperilaku dalam 'mode kompatibilitas' yang membuat Support SQLite dan SQLite Driver API berfungsi seperti yang diharapkan. Hal ini memungkinkan migrasi inkremental sehingga Anda tidak perlu mengonversi semua penggunaan Support SQLite ke SQLite Driver dalam satu perubahan.
Menggunakan Room SQLite Wrapper (Opsional)
Artefak androidx.room:room-sqlite-wrapper menyediakan API untuk menjembatani antara 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 (sering diperoleh dari
RoomDatabase.openHelper.writableDatabase) saat mengadopsi SQLiteDriver,
terutama untuk codebase dengan penggunaan SupportSQLite API yang luas yang ingin
menggunakan BundledSQLiteDriver.
Mengonversi Subclass Migrasi
Subclass migrasi harus dimigrasikan ke rekan driver SQLite:
Multiplatform Kotlin
Subclass migrasi
object Migration_1_2 : Migration(1, 2) {
override fun migrate(connection: SQLiteConnection) {
// …
}
}
Subclass spesifikasi migrasi otomatis
class AutoMigrationSpec_1_2 : AutoMigrationSpec {
override 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 rekan driver SQLite:
Multiplatform Kotlin
object MyRoomCallback : RoomDatabase.Callback() {
override fun onCreate(connection: SQLiteConnection) {
// …
}
override fun onDestructiveMigration(connection: SQLiteConnection) {
// …
}
override 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 diberi anotasi 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 todoDao().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 mendapatkan manfaat dari library kotlinx.coroutines asinkron yang kaya fitur yang ditawarkan Kotlin untuk beberapa platform. Untuk fungsi yang optimal, fungsi suspend diterapkan untuk DAO yang dikompilasi dalam project KMP, kecuali 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 Alur
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 transaction API
Database transaction API untuk Room KMP dapat membedakan antara transaksi tulis (useWriterConnection) dan baca (useReaderConnection).
withTransaction{ … }Multiplatform Kotlin
val database: RoomDatabase = …
database.useWriterConnection { transactor ->
transactor.immediateTransaction {
// perform database operations in transaction
}
}
Khusus Android
val database: RoomDatabase = …
database.withTransaction {
// perform database operations in transaction
}
Menulis transaksi
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 hanya dengan nama playlist dan playlist tidak ada, maka tidak ada operasi tulis (hapus) yang diperlukan.exclusiveTransaction: Mode ini berperilaku identik denganimmediateTransactiondalam mode WAL. Dalam mode penjurnalan lainnya, mode ini mencegah koneksi database lain membaca database saat transaksi sedang berlangsung.
Membaca transaksi
Gunakan transaksi baca untuk membaca dari database beberapa kali secara konsisten. Misalnya, jika Anda memiliki dua kueri terpisah atau lebih dan Anda tidak menggunakan klausa JOIN. Hanya transaksi yang ditangguhkan yang diizinkan dalam koneksi pembaca. Mencoba memulai transaksi langsung atau eksklusif dalam koneksi pembaca akan menampilkan pengecualian, karena transaksi ini dianggap sebagai operasi 'tulis'.
val database: RoomDatabase = …
database.useReaderConnection { transactor ->
transactor.deferredTransaction {
// perform database operations in transaction
}
}
Tidak Tersedia di Multiplatform Kotlin
Beberapa API yang tersedia untuk Android tidak tersedia di Multiplatform Kotlin.
Callback Kueri
API berikut untuk mengonfigurasi callback kueri tidak tersedia di umum dan dengan demikian tidak tersedia di platform selain Android.
RoomDatabase.Builder.setQueryCallbackRoomDatabase.QueryCallback
Kami berencana menambahkan dukungan untuk callback kueri di Room versi mendatang.
API untuk mengonfigurasi RoomDatabase dengan callback kueri
RoomDatabase.Builder.setQueryCallback bersama dengan antarmuka callback
RoomDatabase.QueryCallback tidak tersedia di umum dan dengan demikian tidak tersedia
di platform lain selain Android.
Database Penutupan Otomatis
API untuk mengaktifkan penutupan otomatis setelah waktu tunggu, RoomDatabase.Builder.setAutoCloseTimeout, hanya tersedia di Android dan tidak tersedia di platform lain.
Database dalam Bentuk Paket
API berikut untuk membuat RoomDatabase menggunakan database yang ada (yaitu database dalam bentuk paket) tidak tersedia di umum dan dengan demikian tidak tersedia di platform lain selain Android. API ini adalah:
RoomDatabase.Builder.createFromAssetRoomDatabase.Builder.createFromFileRoomDatabase.Builder.createFromInputStreamRoomDatabase.PrepackagedDatabaseCallback
Kami berencana menambahkan dukungan untuk database dalam bentuk paket di Room versi mendatang.
Invalidasi Multi-Instance
API untuk mengaktifkan invalidasi multi-instance, RoomDatabase.Builder.enableMultiInstanceInvalidation hanya tersedia di Android dan tidak tersedia di umum atau platform lain.
Direkomendasikan untuk Anda
- Catatan: teks link ditampilkan saat JavaScript nonaktif
- Memigrasikan aplikasi yang ada ke Room KMP Codelab
- Mulai Menggunakan KMP Codelab
- Menyimpan data di database lokal dengan Room