غرفة (Kotlin Multiplatform)

توفر مكتبة بقاء الغرفة طبقة تجريدية فوق SQLite للسماح للوصول بفعالية أكبر إلى قاعدة البيانات مع الاستفادة من الإمكانيات الكاملة لـ SQLite. هذا النمط على استخدام الغرف في مشروعات Kotlin متعددة الأنظمة الأساسية (KMP). لمزيد من المعلومات، معلومات حول استخدام الغرفة، يُرجى الاطّلاع على حفظ البيانات في قاعدة بيانات محلية باستخدام الغرفة. أو عيّنات رسمية

إعداد التبعيات

إنّ إصدار الغرفة الحالي الذي يتوافق مع "منصّة KMP" هو 2.7.0-alpha01 أو أعلى.

لإعداد غرفة في مشروع KMP، أضِف التبعيات للعناصر في ملف واحد (build.gradle.kts) للوحدة:

  • androidx.room:room-gradle-plugin - مكوّن Gradle الإضافي لإعداد مخططات الغرف
  • androidx.room:room-compiler - معالج KSP الذي ينشئ الرمز
  • androidx.room:room-runtime - جزء بيئة التشغيل في المكتبة
  • androidx.sqlite:sqlite-bundled - (اختياري) مكتبة SQLite المجمّعة

بالإضافة إلى ذلك، عليك ضبط برنامج تشغيل SQLite في الغرفة. برامج التشغيل هذه مختلفة بناءً على النظام الأساسي المستهدف. عرض عمليات تنفيذ برامج التشغيل للحصول على أوصاف لعمليات تنفيذ برامج التشغيل المتاحة.

للحصول على معلومات إضافية عن الإعداد، يمكنك الاطّلاع على ما يلي:

تحديد فئات قاعدة البيانات

يجب عليك إنشاء فئة قاعدة بيانات تتضمن تعليقات توضيحية باستخدام @Database إلى جانب DAO. والكيانات الموجودة ضمن مجموعة المصادر المشتركة في وحدة KMP المشتركة. جارٍ التقديم هذه الفئات في المصادر المشتركة ستسمح بمشاركتها عبر كل الاستهداف الأساسية.

عند التعريف عن كائن expect باستخدام الواجهة RoomDatabaseConstructor، ينشئ برنامج تجميع الغرف actual وعمليات التنفيذ. قد يُصدر "استوديو Android" تحذيرًا "Expected object 'AppDatabaseConstructor' has no actual declaration in module"; you can suppress the warning with@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>

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

يُرجى العلم أنّه يمكنك اختياريًا استخدام الإعلانات الفعلية / المتوقَّعة. لإنشاء عمليات تنفيذ غرف خاصة بالنظام الأساسي. على سبيل المثال، يمكنك إضافة DAO الخاص بالنظام الأساسي والمحدد في التعليمات البرمجية المشتركة باستخدام expect ثم حدِّد تعريفات actual باستخدام طلبات بحث إضافية في النظام الأساسي مجموعات المصادر.

إنشاء منصة إنشاء قاعدة البيانات

تحتاج إلى تعريف أداة إنشاء قاعدة بيانات لإنشاء مثيل الغرفة على كل نظام أساسي. هذا النمط هو الجزء الوحيد المطلوب من واجهة برمجة التطبيقات في مصدر خاص بالنظام الأساسي بسبب الاختلافات في واجهات برمجة تطبيقات نظام الملفات. على سبيل المثال، في Android، موقع قاعدة البيانات يتم الحصول عليه عادةً من خلال Context.getDatabasePath() API، في حين أن موقع قاعدة البيانات لنظام التشغيل 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)
}

JVM (جهاز كمبيوتر سطح المكتب)

لإنشاء مثيل قاعدة البيانات، قم بتوفير مسار قاعدة بيانات باستخدام 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,
    )
}

مثيل قاعدة البيانات

بعد الحصول على RoomDatabase.Builder من إحدى المنصات الخاصة يمكنك تكوين باقي قاعدة بيانات الغرفة في رمز مشترك مع مثيل قاعدة البيانات الفعلي.

// 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 في النظام الأساسي مجموعات المصادر التي تحدد برنامج تشغيل خاصًا بالنظام الأساسي. على 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")
        }
    }
}

الاختلافات

تم تطوير الغرفة في الأصل كمكتبة Android وتم نقلها لاحقًا إلى برنامج KMP مع التركيز على التوافق مع واجهة برمجة التطبيقات. يختلف إصدار KMP من الغرفة إلى حد ما بين الأنظمة الأساسية والإصدار الخاص بـ 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() { // … }
}

تستفيد الغرفة من مكتبة kotlinx.coroutines غير المتزامنة المليئة بالميزات. التي توفرها لغة Kotlin على أنظمة أساسية متعددة. للحصول على وظائف مثالية، suspend يتم فرض الدوال على أنظمة DAO التي تم تجميعها في مشروع KMP، باستثناء أنظمة DAO الخاصة بنظام Android للحفاظ على التوافق مع الأنظمة القديمة أو قاعدة التعليمات البرمجية.

الاختلافات في الميزات مع KMP

يوضّح هذا القسم أوجه اختلاف الميزات بين "منصة إدارة المحتوى" (KMP) ونظام Android الأساسي. من Google Room.

دوال @RawQuery DAO

الدوال التي تمت إضافة تعليقات توضيحية إليها باستخدام @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

وننوي إتاحة ميزة معاودة الاتصال بطلب البحث في إصدار مستقبلي من الغرفة.

واجهة برمجة التطبيقات لإعداد RoomDatabase باستخدام طلب معاودة الاتصال لطلب بحث RoomDatabase.Builder.setQueryCallback مع واجهة معاودة الاتصال RoomDatabase.QueryCallback غير متاحة بشكل مشترَك، وبالتالي فهي غير متوفّرة. على أنظمة أساسية أخرى غير Android

إغلاق قاعدة البيانات تلقائيًا

واجهة برمجة التطبيقات لتفعيل الإغلاق التلقائي بعد انتهاء المهلة، تطبيق "RoomDatabase.Builder.setAutoCloseTimeout" متوفّر على Android فقط غير متوفّرة في المنصات الأخرى

قاعدة بيانات ما قبل الحزمة

واجهات برمجة التطبيقات التالية لإنشاء RoomDatabase باستخدام قاعدة بيانات حالية (أي قاعدة بيانات مجمعة مسبقًا) غير متوفرة بشكل عام وبالتالي لا تتوفر في الأنظمة الأساسية الأخرى بخلاف Android. واجهات برمجة التطبيقات هذه هي:

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

نعتزم إضافة دعم لقواعد البيانات المعبأة مسبقًا في إصدار مستقبلي من غرفة.

إبطال متعدِّد المثيلات

واجهة برمجة التطبيقات لتفعيل إلغاء المثيلات المتعددة، RoomDatabase.Builder.enableMultiInstanceInvalidation متوفر فقط على Android ولا يتوفر في أنظمة التشغيل المشتركة أو الأنظمة الأساسية الأخرى.