نقل بيانات الغرفة إلى نظام أساسي متعدّد لغة Kotlin

يوضِّح هذا المستند كيفية نقل بيانات غرفة حالية إلى غرفة واحدة التي تستخدم Kotlin Multiplatform (KMP).

نقل استخدامات الغرفة في قاعدة رموز Android حالية إلى منصّة KMP مشتركة مشتركة قد تختلف اختلافًا كبيرًا في مستوى الصعوبة اعتمادًا على واجهات برمجة تطبيقات الغرفة المستخدمة أو ما إذا فإن قاعدة الأكواد تستخدم الكوروتينات بالفعل. يقدّم هذا القسم بعض الإرشادات والنصائح. عند محاولة نقل استخدامات الغرفة إلى وحدة شائعة الاستخدام.

من المهم أن تتعرف أولاً على الاختلافات الميزات بين إصدار Android من الغرفة وإصدار KMP إلى جانب الإعداد المتضمنة. وفي جوهرها، تتضمن عملية الهجرة الناجحة إعادة الهيكلة استخدامات واجهات برمجة تطبيقات SupportSQLite* واستبدالها بواجهات SQLite Driver APIs إلى جانب نقل بيانات الغرفة (فئة @Database تتضمن تعليقات توضيحية، DAO، والكيانات وما إلى ذلك) إلى تعليمات برمجية مشتركة.

يُرجى مراجعة المعلومات التالية قبل المتابعة:

توضح الأقسام التالية الخطوات المختلفة المطلوبة لتحقيق نجاح وترحيلها.

الانتقال من Support SQLite إلى برنامج تشغيل SQLite

إنّ واجهات برمجة التطبيقات في androidx.sqlite.db مخصّصة لنظام التشغيل Android فقط، وأي استخدامات يجب أن تكون باستخدام واجهات برمجة تطبيقات SQLite Driver. للتوافق مع الأنظمة القديمة، وما دامت يتم ضبط RoomDatabase باستخدام SupportSQLiteOpenHelper.Factory (أي لم يتم ضبط SQLiteDriver)، ثم تعمل الغرفة في "وضع التوافق". أين كل من دعم واجهات برمجة تطبيقات SQLite وSQLite Driver API يعملان كما هو متوقع. وهذا يمكّن وعمليات ترحيل تدريجية بحيث لا تحتاج إلى تحويل كل Support SQLite استخدامات SQLite Driver في تغيير واحد.

الأمثلة التالية هي الاستخدامات الشائعة لـ Support SQLite وSQLite نظراء السائقين:

دعم SQLite (من)

تنفيذ طلب بحث بدون العثور على نتائج

val database: SupportSQLiteDatabase = ...
database.execSQL("ALTER TABLE ...")

تنفيذ طلب بحث بدون وسيطات

val database: SupportSQLiteDatabase = ...
database.query("SELECT * FROM Pet").use { cursor ->
  while (cusor.moveToNext()) {
    // read columns
    cursor.getInt(0)
    cursor.getString(1)
  }
}

تنفيذ طلب بحث باستخدام النتيجة والوسيطات

database.query("SELECT * FROM Pet WHERE id = ?", id).use { cursor ->
  if (cursor.moveToNext()) {
    // row found, read columns
  } else {
    // row not found
  }
}

برنامج تشغيل SQLite (إلى)

تنفيذ طلب بحث بدون العثور على نتائج

val connection: SQLiteConnection = ...
connection.execSQL("ALTER TABLE ...")

تنفيذ طلب بحث بدون وسيطات

val connection: SQLiteConnection = ...
connection.prepare("SELECT * FROM Pet").use { statement ->
  while (statement.step()) {
    // read columns
    statement.getInt(0)
    statement.getText(1)
  }
}

تنفيذ طلب بحث باستخدام النتيجة والوسيطات

connection.prepare("SELECT * FROM Pet WHERE id = ?").use { statement ->
  statement.bindInt(1, id)
  if (statement.step()) {
    // row found, read columns
  } else {
    // row not found
  }
}

تتوفّر واجهات برمجة التطبيقات لمعاملات قاعدة البيانات مباشرةً في SupportSQLiteDatabase مع beginTransaction() وsetTransactionSuccessful() وendTransaction() وهي متوفِّرة أيضًا من خلال الغرفة باستخدام "runInTransaction()". نقل هذه البيانات الاستخدام لواجهات برمجة تطبيقات SQLite Driver.

دعم SQLite (من)

إجراء معاملة (باستخدام "RoomDatabase")

val database: RoomDatabase = ...
database.runInTransaction {
  // perform database operations in transaction
}

إجراء معاملة (باستخدام "SupportSQLiteDatabase")

val database: SupportSQLiteDatabase = ...
database.beginTransaction()
try {
  // perform database operations in transaction
  database.setTransactionSuccessful()
} finally {
  database.endTransaction()
}

برنامج تشغيل SQLite (إلى)

إجراء معاملة (باستخدام "RoomDatabase")

val database: RoomDatabase = ...
database.useWriterConnection { transactor ->
  transactor.immediateTransaction {
    // perform database operations in transaction
  }
}

إجراء معاملة (باستخدام "SQLiteConnection")

val connection: SQLiteConnection = ...
connection.execSQL("BEGIN IMMEDIATE TRANSACTION")
try {
  // perform database operations in transaction
  connection.execSQL("END TRANSACTION")
} catch(t: Throwable) {
  connection.execSQL("ROLLBACK TRANSACTION")
}

يجب أيضًا نقل العديد من عمليات إلغاء معاودة الاتصال إلى نظرائها من السائقين:

دعم SQLite (من)

الفئات الفرعية لنقل البيانات

object Migration_1_2 : Migration(1, 2) {
  override fun migrate(db: SupportSQLiteDatabase) {
    // ...
  }
}

الفئات الفرعية لمواصفات النقل التلقائي

class AutoMigrationSpec_1_2 : AutoMigrationSpec {
  override fun onPostMigrate(db: SupportSQLiteDatabase) {
    // ...
  }
}

الفئات الفرعية لمعاودة الاتصال بقاعدة البيانات

object MyRoomCallback : RoomDatabase.Callback {
  override fun onCreate(db: SupportSQLiteDatabase) {
    // ...
  }

  override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
    // ...
  }

  override fun onOpen(db: SupportSQLiteDatabase) {
    // ...
  }
}

برنامج تشغيل SQLite (إلى)

الفئات الفرعية لنقل البيانات

object Migration_1_2 : Migration(1, 2) {
  override fun migrate(connection: SQLiteConnection) {
    // ...
  }
}

الفئات الفرعية لمواصفات النقل التلقائي

class AutoMigrationSpec_1_2 : AutoMigrationSpec {
  override fun onPostMigrate(connection: SQLiteConnection) {
    // ...
  }
}

الفئات الفرعية لمعاودة الاتصال بقاعدة البيانات

object MyRoomCallback : RoomDatabase.Callback {
  override fun onCreate(connection: SQLiteConnection) {
    // ...
  }

  override fun onDestructiveMigration(connection: SQLiteConnection) {
    // ...
  }

  override fun onOpen(connection: SQLiteConnection) {
    // ...
  }
}

للتلخيص، استبدل استخدامات SQLiteDatabase بـ SQLiteConnection عند لا تتوفر RoomDatabase، كما هو الحال في عمليات إلغاء معاودة الاتصال (onMigrate، onCreate، إلخ). في حال توفّر RoomDatabase، يمكنك الوصول إلى المعرّف الأساسي. اتصال قاعدة بيانات باستخدام RoomDatabase.useReaderConnection RoomDatabase.useWriterConnection بدلاً من RoomDatabase.openHelper.writtableDatabase

تحويل دوال DAO الكتلية إلى دوال التعليق

يعتمد إصدار KMP من الغرفة على الكوروتين لإجراء عمليات الإدخال والإخراج. العمليات على CoroutineContext التي تم إعدادها. هذا يعني أنك إلى ترحيل أي دوال DAO محظورة لتعليق الدوال.

حظر دالة DAO (من)

@Query("SELECT * FROM Todo")
fun getAllTodos(): List<Todo>

تعليق الدالة DAO (إلى)

@Query("SELECT * FROM Todo")
suspend fun getAllTodos(): List<Todo>

يمكن نقل دوال المنع الحالية DAO إلى تعليق الدوال إذا كان قاعدة الرموز الحالية لا تتضمّن الكوروتينات. يمكنك الرجوع إلى مقالة الكوروتينات في Android لبدء استخدام الكوروتين. في قاعدة التعليمات البرمجية.

تحويل أنواع الإرجاع التفاعلية إلى تدفق

لا يلزم أن تكون جميع دوال DAO دوال تعليق. دوال DAO التي تُرجع يجب عدم تحويل الأنواع التفاعلية، مثل LiveData أو Flowable في RxJava. تعليق الدوال. في المقابل، بعض الأنواع، مثل LiveData، لا تُعدّ KMP. متوافقة. يجب نقل دوال DAO ذات أنواع الإرجاع التفاعلية إلى تدفقات الكوروتين.

نوع KMP غير متوافق (من)

@Query("SELECT * FROM Todo")
fun getTodosLiveData(): LiveData<List<Todo>>

نوع KMP المتوافق (إلى)

@Query("SELECT * FROM Todo")
fun getTodosFlow(): Flow<List<Todo>>

يُرجى الرجوع إلى التدفقات في Android لبدء استخدام ميزة "التدفق" في أو قاعدة التعليمات البرمجية.

ضبط سياق الكوروتين (اختياري)

يمكن إعداد RoomDatabase بشكل اختياري مع التطبيق المشترك تنفيذين يستخدمان RoomDatabase.Builder.setQueryExecutor() لتنفيذ قاعدة البيانات العمليات التجارية. بما أنّ مدراء التنفيذ غير متوافقين مع "منصّة KMP"، يجب أن تتوفّر setQueryExecutor() في الغرفة. واجهة برمجة التطبيقات غير متاحة للمصادر الشائعة. بدلاً من ذلك RoomDatabase يجب مع CoroutineContext. يمكن تعيين السياق باستخدام RoomDatabase.Builder.setCoroutineContext()، إذا لم يتم ضبط أي قيمة، يتم سيتم ضبط الإعدادات التلقائية لـ "RoomDatabase" على استخدام "Dispatchers.IO".

إعداد برنامج تشغيل SQLite

وبعد نقل استخدامات Support SQLite إلى واجهات برمجة تطبيقات SQLite Driver، لن يتم نقل يجب إعداد برنامج التشغيل باستخدام RoomDatabase.Builder.setDriver. تشير رسالة الأشكال البيانية برنامج التشغيل الموصى به هو BundledSQLiteDriver. اطّلِع على عمليات تنفيذ برامج التشغيل لـ أوصاف لعمليات تنفيذ برامج التشغيل المتاحة.

تم ضبط SupportSQLiteOpenHelper.Factory المخصّص باستخدام RoomDatabase.Builder.openHelperFactory() غير متوافقة في KMP، الميزات التي يقدمها المساعد المفتوح المخصص يجب إعادة تنفيذها مع واجهات برنامج تشغيل SQLite.

طلبات نقل بيانات الغرفة

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

// shared/src/commonMain/kotlin/Database.kt

@Database(entities = [TodoEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
  abstract fun getDao(): TodoDao
  abstract fun getBlockingDao(): BlockingTodoDao
}

@Dao
interface TodoDao {
    @Query("SELECT count(*) FROM TodoEntity")
    suspend fun count(): Int
}

@Dao
expect interface BlockingTodoDao
// shared/src/androidMain/kotlin/BlockingTodoDao.kt

@Dao
actual interface BlockingTodoDao {
    @Query("SELECT count(*) FROM TodoEntity")
    fun count(): Int
}