توفّر مكتبة Room للبيانات الثابتة طبقة تجريدية فوق SQLite للسماح بالوصول إلى قاعدة البيانات بشكل أكثر فعالية مع الاستفادة من إمكانات SQLite الكاملة. تركّز هذه الصفحة على استخدام Room في مشاريع Kotlin Multiplatform (KMP). لمزيد من المعلومات حول استخدام Room، يمكنك الاطّلاع على حفظ البيانات في قاعدة بيانات محلية باستخدام Room أو النماذج الرسمية.
إعداد التبعيات
لإعداد Room في مشروع KMP، أضِف الاعتماديات الخاصة بالعناصر في ملف build.gradle.kts
لوحدة KMP.
حدِّد الاعتماديات في ملف libs.versions.toml
:
[versions]
room = "2.7.2"
sqlite = "2.5.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" }
أضِف Room Gradle Plugin لإعداد مخططات Room ومكوّن KSP الإضافي.
plugins {
alias(libs.plugins.ksp)
alias(libs.plugins.androidx.room)
}
أضِف تبعية وقت التشغيل الخاصة بمكتبة Room ومكتبة SQLite المجمَّعة:
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)
}
أضِف تبعيات KSP إلى حزمة الجذر dependencies
. يُرجى العِلم أنّه عليك إضافة جميع الاستهدافات التي يستخدمها تطبيقك. لمزيد من المعلومات، يُرجى الاطّلاع على KSP مع Kotlin Multiplatform.
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
}
حدِّد دليل مخطط Room. لمزيد من المعلومات، يُرجى الاطّلاع على ضبط موقع المخطط باستخدام إضافة Room Gradle.
room {
schemaDirectory("$projectDir/schemas")
}
تحديد فئات قاعدة البيانات
عليك إنشاء فئة قاعدة بيانات مزوّدة بالتعليق التوضيحي @Database
بالإضافة إلى عناصر DAO
وعناصر داخل مجموعة المصادر الشائعة لوحدة KMP المشترَكة. سيؤدي وضع هذه الفئات في مصادر مشتركة إلى إتاحة مشاركتها على جميع المنصات المستهدَفة.
// 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
}
عند تعريف عنصر expect
باستخدام الواجهة RoomDatabaseConstructor
، ينشئ برنامج التجميع في Room عمليات التنفيذ actual
. قد يعرض "استوديو Android" التحذير التالي، ويمكنك إيقافه باستخدام @Suppress("KotlinNoActualForExpect")
:
Expected object 'AppDatabaseConstructor' has no actual declaration in module`
بعد ذلك، حدِّد واجهة DAO جديدة أو انقل واجهة حالية إلى
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>>
}
حدِّد العناصر أو انقلها إلى commonMain
:
// shared/src/commonMain/kotlin/TodoEntity.kt
@Entity
data class TodoEntity(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val title: String,
val content: String
)
إنشاء أداة إنشاء قاعدة البيانات الخاصة بالنظام الأساسي
عليك تحديد أداة إنشاء قاعدة بيانات لإنشاء مثيل من Room على كل منصة. هذا هو الجزء الوحيد من واجهة برمجة التطبيقات الذي يجب أن يكون في مجموعات المصادر الخاصة بالمنصة بسبب الاختلافات في واجهات برمجة التطبيقات لنظام الملفات.
Android
على Android، يتم عادةً الحصول على موقع قاعدة البيانات من خلال واجهة برمجة التطبيقات
Context.getDatabasePath()
. لإنشاء مثيل قاعدة البيانات، حدِّد Context
مع مسار قاعدة البيانات.
// 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
لإنشاء مثيل قاعدة البيانات على iOS، قدِّم مسار قاعدة بيانات باستخدام NSFileManager
، والذي يكون عادةً في 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)
}
آلة جافا الافتراضية (على الكمبيوتر)
لإنشاء مثيل قاعدة البيانات، قدِّم مسار قاعدة بيانات باستخدام واجهات برمجة تطبيقات Java أو 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,
)
}
إنشاء مثيل لقاعدة البيانات
بعد الحصول على RoomDatabase.Builder
من أحد أدوات الإنشاء الخاصة بالمنصة، يمكنك ضبط بقية قاعدة بيانات Room في الرمز البرمجي المشترك مع إنشاء مثيل قاعدة البيانات الفعلي.
// shared/src/commonMain/kotlin/Database.kt
fun getRoomDatabase(
builder: RoomDatabase.Builder<AppDatabase>
): AppDatabase {
return builder
.setDriver(BundledSQLiteDriver())
.setQueryCoroutineContext(Dispatchers.IO)
.build()
}
اختيار برنامج تشغيل SQLite
يستدعي مقتطف الرمز البرمجي السابق الدالة الإنشائية setDriver
لتحديد برنامج تشغيل SQLite الذي يجب أن تستخدمه قاعدة بيانات Room. وتختلف هذه برامج التشغيل استنادًا إلى النظام الأساسي المستهدف. تستخدم مقتطفات الرموز السابقة BundledSQLiteDriver
.
هذا هو برنامج التشغيل المقترَح الذي يتضمّن SQLite مجمَّعًا من المصدر، ما يوفّر الإصدار الأكثر اتساقًا وحداثة من SQLite على جميع المنصات.
إذا كنت تريد استخدام SQLite الذي يوفّره نظام التشغيل، استخدِم واجهة برمجة التطبيقات setDriver
في مجموعات المصادر الخاصة بالنظام الأساسي والتي تحدّد برنامج تشغيل خاصًا بالنظام الأساسي. راجِع عمليات تنفيذ برامج التشغيل للاطّلاع على أوصاف لعمليات تنفيذ برامج التشغيل المتاحة. يمكنك استخدام أيّ من الخيارَين التاليَين:
AndroidSQLiteDriver
فيandroidMain
NativeSQLiteDriver
فيiosMain
لاستخدام NativeSQLiteDriver
، عليك توفير خيار ربط -lsqlite3
حتى يتم ربط تطبيق 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")
}
}
}
ضبط سياق Coroutine (اختياري)
يمكن ضبط عنصر RoomDatabase
على Android اختياريًا باستخدام منفّذات التطبيقات المشترَكة باستخدام RoomDatabase.Builder.setQueryExecutor()
لتنفيذ عمليات قاعدة البيانات.
بما أنّ المنفّذين غير متوافقين مع KMP، لا تتوفّر واجهة برمجة التطبيقات setQueryExecutor()
الخاصة بمكتبة Room في commonMain
. بدلاً من ذلك، يجب ضبط الكائن RoomDatabase
باستخدام CoroutineContext
، ويمكن ضبطه باستخدام RoomDatabase.Builder.setCoroutineContext()
. في حال عدم ضبط أي سياق، سيتم تلقائيًا استخدام Dispatchers.IO
مع العنصر RoomDatabase
.
إزالة البيانات غير الضرورية والتشويش
إذا كان المشروع مصغّرًا أو مشوّشًا، يجب تضمين قاعدة ProGuard التالية حتى يتمكّن Room من العثور على التنفيذ الذي تم إنشاؤه لتعريف قاعدة البيانات:
-keep class * extends androidx.room.RoomDatabase { <init>(); }
نقل البيانات إلى Kotlin Multiplatform
تم تطوير Room في الأصل كمكتبة Android، وتم نقلها لاحقًا إلى KMP مع التركيز على توافق واجهة برمجة التطبيقات. يختلف إصدار Room المتوافق مع KMP قليلاً بين المنصات وعن الإصدار المتوافق مع Android فقط. وفي ما يلي قائمة بهذه الاختلافات مع وصف لها.
نقل البيانات من Support SQLite إلى SQLite Driver
يجب إعادة تصميم أي استخدامات لـ SupportSQLiteDatabase
وواجهات برمجة التطبيقات الأخرى في androidx.sqlite.db
باستخدام واجهات برمجة التطبيقات SQLite Driver، لأنّ واجهات برمجة التطبيقات في androidx.sqlite.db
مخصّصة لنظام التشغيل Android فقط (يُرجى ملاحظة الحزمة المختلفة عن حزمة KMP).
لضمان التوافق مع الإصدارات القديمة، وطالما تم ضبط RoomDatabase
باستخدام SupportSQLiteOpenHelper.Factory
(على سبيل المثال، لم يتم ضبط SQLiteDriver
)، سيعمل Room في "وضع التوافق" حيث تعمل كل من واجهات برمجة التطبيقات Support SQLite وSQLite Driver على النحو المتوقّع. يتيح ذلك عمليات نقل تدريجية حتى لا تحتاج إلى تحويل جميع استخدامات Support SQLite إلى SQLite Driver في تغيير واحد.
استخدام برنامج تضمين Room SQLite (اختياري)
يوفر العنصر androidx.room:room-sqlite-wrapper
واجهات برمجة تطبيقات لربط SQLiteDriver
وSupportSQLiteDatabase
أثناء عملية النقل.
للحصول على SupportSQLiteDatabase
من RoomDatabase
تم إعداده باستخدام SQLiteDriver
، استخدِم دالة الإضافة الجديدة RoomDatabase.getSupportWrapper()
. يساعد برنامج التغليف المتوافق هذا في الحفاظ على الاستخدامات الحالية لـ SupportSQLiteDatabase
(التي يتم الحصول عليها غالبًا من RoomDatabase.openHelper.writableDatabase
) مع اعتماد SQLiteDriver
، خاصةً لقواعد الرموز التي تتضمّن استخدامات واسعة النطاق لواجهة برمجة التطبيقات SupportSQLite
وتريد استخدام BundledSQLiteDriver
.
فئات فرعية لعمليات نقل البيانات
يجب نقل الفئات الفرعية لعمليات نقل البيانات إلى نظيراتها في برنامج تشغيل SQLite:
Kotlin Multiplatform
فئات فرعية لعمليات نقل البيانات
object Migration_1_2 : Migration(1, 2) {
override fun migrate(connection: SQLiteConnection) {
// …
}
}
فئات فرعية لمواصفات النقل التلقائي للبيانات
class AutoMigrationSpec_1_2 : AutoMigrationSpec {
override fun onPostMigrate(connection: SQLiteConnection) {
// …
}
}
على أجهزة Android فقط
فئات فرعية لعمليات نقل البيانات
object Migration_1_2 : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
// …
}
}
فئات فرعية لمواصفات النقل التلقائي للبيانات
class AutoMigrationSpec_1_2 : AutoMigrationSpec {
override fun onPostMigrate(db: SupportSQLiteDatabase) {
// …
}
}
معاودة الاتصال بقاعدة البيانات المحوَّلة
يجب نقل عمليات معاودة الاتصال بقاعدة البيانات إلى نظيراتها في برنامج تشغيل SQLite:
Kotlin Multiplatform
object MyRoomCallback : RoomDatabase.Callback() {
override fun onCreate(connection: SQLiteConnection) {
// …
}
override fun onDestructiveMigration(connection: SQLiteConnection) {
// …
}
override fun onOpen(connection: SQLiteConnection) {
// …
}
}
على أجهزة Android فقط
object MyRoomCallback : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
// …
}
override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
// …
}
override fun onOpen(db: SupportSQLiteDatabase) {
// …
}
}
تحويل دوال @RawQuery
DAO
يجب أن تحدّد الدوال التي تمّت إضافة التعليق التوضيحي @RawQuery
إليها وتمّت ترجمتها للأنظمة الأساسية غير Android
معلَمة من النوع RoomRawQuery
بدلاً من SupportSQLiteQuery
.
Kotlin Multiplatform
تحديد طلب البحث الأولي
@Dao
interface TodoDao {
@RawQuery
suspend fun getTodos(query: RoomRawQuery): List<TodoEntity>
}
يمكن بعد ذلك استخدام RoomRawQuery
لإنشاء طلب في وقت التشغيل:
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)
}
على أجهزة Android فقط
تحديد طلب البحث الأولي
@Dao
interface TodoDao {
@RawQuery
suspend fun getTodos(query: SupportSQLiteQuery): List<TodoEntity>
}
يمكن بعد ذلك استخدام SimpleSQLiteQuery
لإنشاء طلب في وقت التشغيل:
suspend fun AndroidOnlyDao.getTodosWithLowercaseTitle(title: String): List<TodoEntity> {
val query = SimpleSQLiteQuery(
query = "SELECT * FROM TodoEntity WHERE title = ?",
bindArgs = arrayOf(title.lowercase())
)
return getTodos(query)
}
تحويل دوال DAO التي تحظر
تستفيد الغرفة من مكتبة kotlinx.coroutines
غير متزامنة غنية بالميزات
التي توفّرها Kotlin للعديد من المنصات. للحصول على أفضل وظائف، يتم فرض استخدام دوال suspend
على عناصر الوصول إلى البيانات (DAO) التي تم تجميعها في مشروع KMP، باستثناء عناصر الوصول إلى البيانات (DAO) التي تم تنفيذها في androidMain
للحفاظ على التوافق مع الإصدارات القديمة من قاعدة الرموز الحالية. عند استخدام Room مع KMP، يجب أن تكون جميع دوال DAO التي تم تجميعها للمنصات غير التابعة لنظام Android عبارة عن دوال suspend
.
Kotlin Multiplatform
تعليق طلبات البحث
@Query("SELECT * FROM Todo")
suspend fun getAllTodos(): List<Todo>
تعليق المعاملات
@Transaction
suspend fun transaction() { … }
على أجهزة Android فقط
حظر طلبات البحث
@Query("SELECT * FROM Todo")
fun getAllTodos(): List<Todo>
حظر المعاملات
@Transaction
fun blockingTransaction() { … }
تحويل الأنواع التفاعلية إلى Flow
ليس من الضروري أن تكون جميع دوال DAO دوال تعليق. يجب عدم تحويل دوال DAO التي تعرض أنواعًا تفاعلية، مثل LiveData
أو Flowable
في RxJava، إلى دوال معلّقة. ومع ذلك، لا تتوافق بعض الأنواع، مثل LiveData
، مع KMP. يجب نقل دوال DAO التي تتضمّن أنواع إرجاع تفاعلية إلى تدفقات روتينية فرعية.
Kotlin Multiplatform
أنواع التفاعلات Flows
@Query("SELECT * FROM Todo")
fun getTodosFlow(): Flow<List<Todo>>
على أجهزة Android فقط
أنواع تفاعلية مثل LiveData
أو Flowable
في RxJava
@Query("SELECT * FROM Todo")
fun getTodosLiveData(): LiveData<List<Todo>>
تحويل واجهات برمجة التطبيقات الخاصة بالمعاملات
يمكن لواجهات برمجة التطبيقات الخاصة بمعاملات قاعدة البيانات في Room KMP التمييز بين معاملات الكتابة (useWriterConnection
) والقراءة (useReaderConnection
).
Kotlin Multiplatform
val database: RoomDatabase = …
database.useWriterConnection { transactor ->
transactor.immediateTransaction {
// perform database operations in transaction
}
}
على أجهزة Android فقط
val database: RoomDatabase = …
database.withTransaction {
// perform database operations in transaction
}
كتابة المعاملات
استخدِم عمليات الكتابة للتأكّد من أنّ الاستعلامات المتعدّدة تكتب البيانات بشكل ذري،
ليتمكّن القارئون من الوصول إلى البيانات بشكل متّسق. يمكنك إجراء ذلك باستخدام
useWriterConnection
مع أيّ من أنواع المعاملات الثلاثة:
immediateTransaction
: في وضع تسجيل العمليات قبل تنفيذها (WAL) (الإعداد التلقائي)، يحصل هذا النوع من المعاملات على قفل عند بدء تشغيله، ولكن يمكن للقراء مواصلة القراءة. هذا هو الخيار المفضّل في معظم الحالات.
deferredTransaction
: لن تكتسب المعاملة قفلًا حتى عبارة الكتابة الأولى. استخدِم هذا النوع من المعاملات كتحسين عندما لا تكون متأكدًا مما إذا كانت ستكون هناك حاجة إلى عملية كتابة ضمن المعاملة. على سبيل المثال، إذا بدأت معاملة لحذف أغانٍ من قائمة تشغيل من خلال تقديم اسم قائمة التشغيل فقط وكانت قائمة التشغيل غير متوفّرة، لن تكون هناك حاجة إلى عملية كتابة (حذف).
exclusiveTransaction
: يتطابق سلوك هذا الوضع مع سلوكimmediateTransaction
في وضع WAL. في أوضاع التسجيل الأخرى، يمنع هذا الوضع اتصالات قواعد البيانات الأخرى من قراءة قاعدة البيانات أثناء تنفيذ المعاملة.
قراءة المعاملات
استخدِم معاملات القراءة لقراءة البيانات من قاعدة البيانات بشكل متّسق عدة مرات. على سبيل المثال، عندما يكون لديك طلبا بحث منفصلان أو أكثر ولا تستخدم عبارة JOIN
. يُسمح بالمعاملات المؤجّلة فقط في اتصالات القارئ. سيؤدي محاولة بدء معاملة فورية أو حصرية في اتصال قارئ إلى طرح استثناء، لأنّ هذه العمليات تُعدّ عمليات "كتابة".
val database: RoomDatabase = …
database.useReaderConnection { transactor ->
transactor.deferredTransaction {
// perform database operations in transaction
}
}
غير متوفّرة في Kotlin Multiplatform
بعض واجهات برمجة التطبيقات التي كانت متاحة لنظام التشغيل Android غير متاحة في Kotlin Multiplatform.
معاودة الاتصال بالاستعلام
لا تتوفّر واجهات برمجة التطبيقات التالية لإعداد عمليات معاودة الاتصال الخاصة بطلبات البحث في Common، وبالتالي لا تتوفّر في المنصات الأخرى غير Android.
RoomDatabase.Builder.setQueryCallback
RoomDatabase.QueryCallback
نعتزم إتاحة إمكانية استخدام وظيفة معاودة الاتصال للاستعلام في إصدار مستقبلي من Room.
لا تتوفّر واجهة برمجة التطبيقات التي تتيح ضبط RoomDatabase
باستخدام دالة معاودة الاتصال الخاصة بطلب البحث
RoomDatabase.Builder.setQueryCallback
بالإضافة إلى واجهة معاودة الاتصال
RoomDatabase.QueryCallback
في الأنظمة الأساسية الشائعة، وبالتالي لا تتوفّر
في الأنظمة الأساسية الأخرى غير Android.
قاعدة بيانات الإغلاق التلقائي
لا تتوفّر واجهة برمجة التطبيقات التي تتيح الإغلاق التلقائي بعد انتهاء المهلة، RoomDatabase.Builder.setAutoCloseTimeout
، إلا على Android، ولا تتوفّر على المنصات الأخرى.
قاعدة بيانات الحِزم المُعدّة مسبقًا
إنّ واجهات برمجة التطبيقات التالية لإنشاء RoomDatabase
باستخدام قاعدة بيانات حالية (أي قاعدة بيانات مجمّعة مسبقًا) غير متاحة بشكل عام، وبالتالي فهي غير متاحة في منصات أخرى غير Android. وهذه الواجهات هي:
RoomDatabase.Builder.createFromAsset
RoomDatabase.Builder.createFromFile
RoomDatabase.Builder.createFromInputStream
RoomDatabase.PrepackagedDatabaseCallback
نحن بصدد إتاحة إمكانية استخدام قواعد البيانات المُعدّة مسبقًا في إصدار مستقبلي من Room.
إلغاء صلاحية مثيلات متعددة
تتوفّر واجهة برمجة التطبيقات لتفعيل الإبطال المتعدّد المثيلات،
RoomDatabase.Builder.enableMultiInstanceInvalidation
على Android فقط، ولا تتوفّر على الأنظمة الأساسية الشائعة أو غيرها.
اقتراحات مخصصة لك
- ملاحظة: يتم عرض نص الرابط عندما تكون JavaScript غير مفعّلة
- نقل التطبيقات الحالية إلى دروس Room KMP التطبيقية
- بدء استخدام دروس KMP التطبيقية
- حفظ البيانات في قاعدة بيانات محلية باستخدام Room