توفّر مكتبة Room للحفاظ على البيانات طبقة تجريدية فوق SQLite للسماح بالوصول إلى قاعدة البيانات بشكل أكثر فعالية مع الاستفادة من إمكانات SQLite الكاملة. تركّز هذه الصفحة على استخدام Room في مشاريع Kotlin Multiplatform (KMP). لمزيد من المعلومات حول استخدام Room، اطّلِع على مقالة حفظ البيانات في قاعدة بيانات محلية باستخدام Room أو عيّناتنا الرسمية.
إعداد التبعيات
الإصدار الحالي من Room الذي يتيح استخدام KMP هو 2.7.0-alpha01 أو إصدار أحدث.
لإعداد Room في مشروع KMP، أضِف التبعيات للعناصر في ملف
build.gradle.kts
الخاص بوحدتك:
-
androidx.room:room-gradle-plugin
- المكوّن الإضافي Gradle لضبط مخطّطات Room androidx.room:room-compiler
: معالج KSP الذي ينشئ الرمزandroidx.room:room-runtime
: جزء وقت التشغيل من المكتبةandroidx.sqlite:sqlite-bundled
- (اختياري) مكتبة SQLite المُضمّنة
بالإضافة إلى ذلك، عليك ضبط برنامج تشغيل SQLite في Room. تختلف هذه برامج التشغيل استنادًا إلى المنصة المستهدَفة. اطّلِع على عمليات تنفيذ برامج تشغيل الأجهزة للحصول على أوصاف لعمليات تنفيذ برامج تشغيل الأجهزة المتاحة.
للحصول على معلومات إضافية حول الإعداد، يُرجى الاطّلاع على ما يلي:
- ضبط موقع المخطّط باستخدام المكوّن الإضافي Room Gradle
- KSP مع Kotlin Multiplatform
- إضافة التبعيات في وقت التشغيل
تحديد فئات قاعدة البيانات
عليك إنشاء فئة قاعدة بيانات تمّت إضافة تعليقات توضيحية إليها باستخدام @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> {
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
)
يُرجى العِلم أنّه يمكنك اختياريًا استخدام بيانات actual / expect
لإنشاء عمليات تنفيذ Room خاصة بالنظام الأساسي. على سبيل المثال، يمكنك إضافة
DAO خاص بالنظام الأساسي تم تحديده في رمز عادي باستخدام expect
ثم
تحديد تعريفات actual
باستخدام طلبات بحث إضافية في
مجموعات المصادر الخاصة بالنظام الأساسي.
إنشاء أداة إنشاء قاعدة البيانات
عليك تحديد أداة إنشاء قاعدة بيانات لإنشاء مثيل لـ Room على كل منصة. هذا هو
الجزء الوحيد من واجهة برمجة التطبيقات المطلوب أن يكون في مجموعات ملف المصدر الخاصة بالنظام الأساسي
بسبب الاختلافات في واجهات برمجة تطبيقات نظام الملفات. على سبيل المثال، في Android، يتم عادةً الحصول على موقع قاعدة البيانات من خلال واجهة برمجة التطبيقات
Context.getDatabasePath()
، في حين يتم الحصول على موقع قاعدة البيانات في iOS باستخدام NSFileManager
.
Android
لإنشاء مثيل قاعدة البيانات، حدِّد سياقًا مع ملف قاعدة بيانات المسار.
// 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)
}
آلة Java الافتراضية (لأجهزة الكمبيوتر المكتبي)
لإنشاء مثيل قاعدة البيانات، قدِّم مسار قاعدة بيانات باستخدام واجهات برمجة التطبيقات 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,
)
}
تصغير الحجم وإخفاء مفاتيح فك التشفير
إذا كان المشروع مصغرًا أو مشوّشًا، يجب تضمين قاعدة Proguard التالية لكي تتمكّن Room من العثور على التنفيذ الذي تم إنشاؤه لتعريف قاعدة البيانات:
-keep class * extends androidx.room.RoomDatabase { <init>(); }
إنشاء مثيل لقاعدة البيانات
بعد الحصول على RoomDatabase.Builder
من أحد مبرمجِي 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 على جميع المنصات. إذا
أردت استخدام SQLite المقدَّمة من نظام التشغيل، استخدِم واجهة برمجة التطبيقات setDriver
في مجموعات setDriver
الخاصة بالنظام الأساسي والتي تحدِّد برنامج تشغيل خاص بالنظام الأساسي. بالنسبة إلى Android، يمكنك استخدام رمز
AndroidSQLiteDriver
، بينما يمكنك استخدام رمز NativeSQLiteDriver
على iOS. لاستخدام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 وتم نقلها لاحقًا إلى KMP مع التركيز على توافق واجهة برمجة التطبيقات. يختلف إصدار KMP من Room نوعًا ما بين الأنظمة الأساسية والإصدار المخصّص لنظام التشغيل Android. يتم سرد هذه الاختلافات ووصفها على النحو التالي.
حظر دوال DAO
عند استخدام Room لـ KMP، يجب أن تكون جميع وظائف DAO التي تم تجميعها لمنصّات غير Android
دالات 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
وظائف DAO المجمّعة في مشروع KMP، باستثناء
DAO الخاصة بنظام التشغيل Android للحفاظ على التوافق مع الإصدارات القديمة
لقاعدة البيانات الحالية.
الاختلافات في الميزات مع KMP
يصف هذا القسم الاختلافات بين ميزات إصدارَي Room المتوافقَين مع KMP ونظام التشغيل Android.
دوال DAO في @RawQuery
بالنسبة إلى الدوال التي تمّت إضافة تعليقات توضيحية لها باستخدام @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)
}
معاودة الاتصال لطلب البحث
لا تتوفّر واجهات برمجة التطبيقات التالية لضبط عمليات استدعاء طلبات البحث بشكل شائع، وبالتالي لا تتوفّر في منصات غير 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، وهي غير متاحة في الأنظمة الأساسية الشائعة أو غيرها.