اتاق را به چند پلتفرم Kotlin منتقل کنید

این سند نحوه انتقال یک پیاده‌سازی اتاق موجود را به یکی که از کاتلین چند پلتفرم (KMP) استفاده می‌کند، توضیح می‌دهد.

انتقال استفاده‌های اتاق در یک پایگاه کد Android موجود به یک ماژول KMP مشترک مشترک، بسته به APIهای اتاق استفاده‌شده یا اینکه پایگاه کد قبلاً از Coroutines استفاده می‌کند، می‌تواند بسیار دشوار باشد. این بخش راهنمایی ها و نکاتی را هنگام تلاش برای انتقال استفاده از اتاق به یک ماژول مشترک ارائه می دهد.

مهم است که ابتدا با تفاوت ها و ویژگی های از دست رفته بین نسخه اندروید اتاق و نسخه KMP همراه با تنظیمات مربوطه آشنا شوید. در اصل، یک مهاجرت موفقیت آمیز شامل بازسازی استفاده از APIهای SupportSQLite* و جایگزینی آنها با APIهای درایور SQLite همراه با انتقال اعلانات اتاق (کلاس مشروح @Database ، DAOها، نهادها و غیره) به کدهای رایج است.

قبل از ادامه، دوباره به اطلاعات زیر مراجعه کنید:

بخش های بعدی مراحل مختلف مورد نیاز برای مهاجرت موفقیت آمیز را شرح می دهد.

از پشتیبانی SQLite به درایور SQLite مهاجرت کنید

APIهای موجود در androidx.sqlite.db فقط برای اندروید هستند و هر گونه استفاده باید با APIهای درایور SQLite اصلاح شود. برای سازگاری با عقب، و تا زمانی که RoomDatabase با یک SupportSQLiteOpenHelper.Factory پیکربندی شده است (یعنی SQLiteDriver تنظیم نشده است)، اتاق در «حالت سازگاری» رفتار می‌کند که در آن هر دو API درایور SQLite و SQLite پشتیبانی می‌کنند. این امکان مهاجرت های افزایشی را فراهم می کند تا نیازی به تبدیل تمام استفاده های پشتیبانی SQLite خود به درایور SQLite در یک تغییر نداشته باشید.

مثال‌های زیر کاربردهای رایج پشتیبانی SQLite و همتایان SQLite Driver آن‌ها هستند:

پشتیبانی از 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
  }
}

APIهای تراکنش پایگاه داده مستقیماً در SupportSQLiteDatabase با beginTransaction() ، setTransactionSuccessful() و endTransaction() موجود هستند. آنها همچنین از طریق Room با استفاده از runInTransaction() در دسترس هستند. این موارد استفاده را به APIهای درایور SQLite منتقل کنید.

پشتیبانی از 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 موجود برای تعلیق توابع می‌تواند پیچیده باشد اگر پایگاه کد موجود از قبل دارای کوروتین‌ها نباشد. برای شروع استفاده از کوروتین ها در پایگاه کد خود به Coroutines در Android مراجعه کنید.

انواع برگشتی واکنشی را به Flow تبدیل کنید

لازم نیست همه توابع DAO توابع تعلیق باشند. توابع DAO که انواع واکنشی مانند LiveData یا RxJava's Flowable را برمی گرداند، نباید به توابع تعلیق تبدیل شوند. با این حال، برخی از انواع مانند LiveData با KMP سازگار نیستند. توابع DAO با انواع برگشتی واکنشی باید به جریان های معمولی منتقل شوند.

نوع KMP ناسازگار (از)

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

نوع KMP سازگار (به)

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

برای شروع استفاده از Flows در پایگاه کد خود به Flows در Android مراجعه کنید.

تنظیم زمینه Coroutine (اختیاری)

یک RoomDatabase به صورت اختیاری می تواند با مجریان برنامه های مشترک با استفاده از RoomDatabase.Builder.setQueryExecutor() برای انجام عملیات پایگاه داده پیکربندی شود. از آنجایی که مجری‌ها با KMP سازگار نیستند، API setQueryExecutor() Room برای منابع رایج در دسترس نیست. در عوض RoomDatabase باید با یک CoroutineContext پیکربندی شود. یک متن را می توان با استفاده از RoomDatabase.Builder.setCoroutineContext() تنظیم کرد، اگر هیچ کدام تنظیم نشده باشد، RoomDatabase به طور پیش فرض از Dispatchers.IO استفاده می کند.

یک درایور SQLite تنظیم کنید

هنگامی که استفاده‌های پشتیبانی از SQLite به APIهای درایور SQLite منتقل شدند، باید یک درایور با استفاده از RoomDatabase.Builder.setDriver پیکربندی شود. درایور پیشنهادی BundledSQLiteDriver است. برای توضیحات پیاده سازی درایورهای موجود ، پیاده سازی درایور را ببینید.

Custom SupportSQLiteOpenHelper.Factory پیکربندی شده با استفاده از RoomDatabase.Builder.openHelperFactory() در KMP پشتیبانی نمی‌شود، ویژگی‌های ارائه‌شده توسط کمک‌کننده باز سفارشی باید دوباره با رابط‌های SQLite Driver پیاده‌سازی شوند.

اعلامیه های اتاق جابجایی

هنگامی که اکثر مراحل انتقال کامل شد، می توان تعاریف اتاق را به یک مجموعه منبع مشترک منتقل کرد. توجه داشته باشید که برای جابجایی تدریجی تعاریف مربوط به اتاق می توان از استراتژی های 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
}