این سند نحوه انتقال یک پیادهسازی اتاق موجود را به یکی که از کاتلین چند پلتفرم (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
}