کتابخانه تداوم اتاق یک لایه انتزاعی را بر روی SQLite فراهم می کند تا در عین استفاده از قدرت کامل SQLite، امکان دسترسی قوی تر به پایگاه داده را فراهم کند. این صفحه بر روی استفاده از اتاق در پروژههای چند پلتفرمی Kotlin (KMP) تمرکز دارد. برای اطلاعات بیشتر در مورد استفاده از اتاق، به ذخیره داده ها در پایگاه داده محلی با استفاده از اتاق یا نمونه های رسمی ما مراجعه کنید.
وابستگی ها را تنظیم کنید
برای راهاندازی اتاق در پروژه KMP خود، وابستگیهای مصنوعات را در فایل build.gradle.kts
برای ماژول KMP خود اضافه کنید.
وابستگی ها را در فایل libs.versions.toml
تعریف کنید:
[versions]
room = "2.7.2"
sqlite = "2.5.2"
ksp = "<kotlinCompatibleKspVersion>"
[libraries]
androidx-sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "sqlite" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
# Optional SQLite Wrapper available in version 2.8.0 and higher
androidx-room-sqlite-wrapper = { module = "androidx.room:room-sqlite-wrapper", version.ref = "room" }
[plugins]
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
androidx-room = { id = "androidx.room", version.ref = "room" }
برای پیکربندی طرحواره های اتاق و افزونه KSP ، افزونه Room Gradle را اضافه کنید
plugins {
alias(libs.plugins.ksp)
alias(libs.plugins.androidx.room)
}
وابستگی Runtime Room و کتابخانه SQLite همراه را اضافه کنید:
commonMain.dependencies {
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.sqlite.bundled)
}
// Optional when using Room SQLite Wrapper
androidMain.dependencies {
implementation(libs.androidx.room.sqlite.wrapper)
}
وابستگی های KSP را به بلوک dependencies
ریشه اضافه کنید. توجه داشته باشید که باید تمام اهداف مورد استفاده برنامه خود را اضافه کنید. برای اطلاعات بیشتر، KSP را با Kotlin Multiplatform بررسی کنید.
dependencies {
add("kspAndroid", libs.androidx.room.compiler)
add("kspIosSimulatorArm64", libs.androidx.room.compiler)
add("kspIosX64", libs.androidx.room.compiler)
add("kspIosArm64", libs.androidx.room.compiler)
// Add any other platform target you use in your project, for example kspDesktop
}
دایرکتوری طرحواره اتاق را تعریف کنید. برای اطلاعات بیشتر، به تنظیم مکان طرح با استفاده از پلاگین Room Gradle مراجعه کنید.
room {
schemaDirectory("$projectDir/schemas")
}
کلاس های پایگاه داده را تعریف کنید
شما باید یک کلاس پایگاه داده مشروح شده با @Database
همراه با DAO ها و موجودیت ها در داخل مجموعه منبع مشترک ماژول KMP مشترک خود ایجاد کنید. قرار دادن این کلاس ها در منابع مشترک به آنها اجازه می دهد تا در تمام پلتفرم های هدف به اشتراک گذاشته شوند.
// 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("KotlinNoActualForExpect")
expect object AppDatabaseConstructor : RoomDatabaseConstructor<AppDatabase> {
override fun initialize(): AppDatabase
}
هنگامی که یک شیء expect
با رابط RoomDatabaseConstructor
اعلام می کنید، کامپایلر Room پیاده سازی های actual
را ایجاد می کند. Android Studio ممکن است اخطار زیر را صادر کند، که میتوانید با @Suppress("KotlinNoActualForExpect")
آن را سرکوب کنید:
Expected object 'AppDatabaseConstructor' has no actual declaration in module`
سپس، یا یک رابط DAO جدید تعریف کنید یا یک رابط موجود را به commonMain
منتقل کنید:
// shared/src/commonMain/kotlin/TodoDao.kt
@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>>
}
موجودیت های خود را به commonMain
تعریف یا منتقل کنید:
// shared/src/commonMain/kotlin/TodoEntity.kt
@Entity
data class TodoEntity(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val title: String,
val content: String
)
سازنده پایگاه داده مخصوص پلتفرم را ایجاد کنید
برای نمونه سازی Room در هر پلتفرم باید یک سازنده پایگاه داده تعریف کنید. این تنها بخشی از API است که به دلیل تفاوت در API های سیستم فایل، باید در مجموعه های منبع خاص پلت فرم باشد.
اندروید
در اندروید، مکان پایگاه داده معمولاً از طریق Context.getDatabasePath()
API بدست می آید. برای ایجاد نمونه پایگاه داده، یک Context
به همراه مسیر پایگاه داده مشخص کنید.
// shared/src/androidMain/kotlin/Database.android.kt
fun getDatabaseBuilder(context: Context): RoomDatabase.Builder<AppDatabase> {
val appContext = context.applicationContext
val dbFile = appContext.getDatabasePath("my_room.db")
return Room.databaseBuilder<AppDatabase>(
context = appContext,
name = dbFile.absolutePath
)
}
iOS
برای ایجاد نمونه پایگاه داده در iOS، یک مسیر پایگاه داده با استفاده از NSFileManager
، که معمولاً در NSDocumentDirectory
قرار دارد، ارائه دهید.
// shared/src/iosMain/kotlin/Database.ios.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 API ارائه کنید.
// shared/src/jvmMain/kotlin/Database.desktop.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
.setDriver(BundledSQLiteDriver())
.setQueryCoroutineContext(Dispatchers.IO)
.build()
}
درایور SQLite را انتخاب کنید
قطعه کد قبلی تابع سازنده setDriver
را فراخوانی می کند تا مشخص کند پایگاه داده اتاق باید از چه درایور SQLite استفاده کند. این درایورها بر اساس پلتفرم هدف متفاوت هستند. قطعه کد قبلی از BundledSQLiteDriver
استفاده می کند. این درایور پیشنهادی است که شامل SQLite است که از منبع کامپایل شده است، که سازگارترین و بهروزترین نسخه SQLite را در همه پلتفرمها ارائه میدهد.
اگر میخواهید از SQLite ارائهشده توسط سیستمعامل استفاده کنید، از setDriver
API در مجموعههای منبع خاص پلتفرم که درایور مخصوص پلتفرم را مشخص میکنند، استفاده کنید. برای توضیحات پیاده سازی درایورهای موجود، پیاده سازی درایور را ببینید. می توانید از یکی از موارد زیر استفاده کنید:
-
AndroidSQLiteDriver
درandroidMain
-
NativeSQLiteDriver
درiosMain
برای استفاده از NativeSQLiteDriver
، باید یک گزینه پیوند دهنده -lsqlite3
ارائه دهید تا برنامه 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")
}
}
}
تنظیم زمینه Coroutine (اختیاری)
یک شی RoomDatabase
در Android میتواند به صورت اختیاری با مجریان برنامه مشترک با استفاده از RoomDatabase.Builder.setQueryExecutor()
برای انجام عملیات پایگاه داده پیکربندی شود.
از آنجایی که مجریها با KMP سازگار نیستند، API setQueryExecutor()
Room در commonMain
در دسترس نیست. در عوض، شی RoomDatabase
باید با یک CoroutineContext
پیکربندی شود، که می تواند با استفاده از RoomDatabase.Builder.setCoroutineContext()
تنظیم شود. اگر زمینه تنظیم نشده باشد، شی RoomDatabase
به طور پیش فرض از Dispatchers.IO
استفاده می کند.
کوچک سازی و مبهم سازی
اگر پروژه کوچک یا مبهم باشد، باید قانون ProGuard زیر را در نظر بگیرید تا Room بتواند پیاده سازی ایجاد شده از تعریف پایگاه داده را پیدا کند:
-keep class * extends androidx.room.RoomDatabase { <init>(); }
به چند پلتفرم Kotlin مهاجرت کنید
Room در ابتدا به عنوان یک کتابخانه اندروید توسعه یافت و بعداً با تمرکز بر سازگاری API به KMP منتقل شد. نسخه KMP اتاق بین پلتفرمها و با نسخه مخصوص اندروید تا حدودی متفاوت است. این تفاوت ها به شرح زیر فهرست شده و شرح داده شده است.
از پشتیبانی SQLite به درایور SQLite مهاجرت کنید
هرگونه استفاده از SupportSQLiteDatabase
و سایر APIها در androidx.sqlite.db
باید با APIهای درایور SQLite اصلاح شود، زیرا APIهای موجود در androidx.sqlite.db
فقط برای Android هستند (به بسته متفاوت از بسته KMP توجه کنید).
برای سازگاری با عقب، و تا زمانی که RoomDatabase
با یک SupportSQLiteOpenHelper.Factory
پیکربندی شده است (به عنوان مثال، SQLiteDriver
تنظیم نشده است)، اتاق در «حالت سازگاری» رفتار میکند که در آن هر دو API درایور SQLite و SQLite مطابق انتظار کار میکنند. این امکان مهاجرت های افزایشی را فراهم می کند تا نیازی به تبدیل تمام استفاده های پشتیبانی SQLite خود به درایور SQLite در یک تغییر نداشته باشید.
تبدیل زیر کلاس های مهاجرت
زیر کلاس های مهاجرت باید به همتایان درایور SQLite منتقل شوند:
چند پلتفرم کاتلین
زیر کلاس های مهاجرت
object Migration_1_2 : Migration(1, 2) {
override fun migrate(connection: SQLiteConnection) {
// …
}
}
زیر کلاس های مشخصات مهاجرت خودکار
class AutoMigrationSpec_1_2 : AutoMigrationSpec {
override fun onPostMigrate(connection: SQLiteConnection) {
// …
}
}
فقط اندروید
زیر کلاس های مهاجرت
object Migration_1_2 : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
// …
}
}
زیر کلاس های مشخصات مهاجرت خودکار
class AutoMigrationSpec_1_2 : AutoMigrationSpec {
override fun onPostMigrate(db: SupportSQLiteDatabase) {
// …
}
}
تبدیل مجدد تماس پایگاه داده
تماس های پایگاه داده باید به همتایان درایور SQLite منتقل شوند:
چند پلتفرم کاتلین
object MyRoomCallback : RoomDatabase.Callback() {
override fun onCreate(connection: SQLiteConnection) {
// …
}
override fun onDestructiveMigration(connection: SQLiteConnection) {
// …
}
override fun onOpen(connection: SQLiteConnection) {
// …
}
}
فقط اندروید
object MyRoomCallback : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
// …
}
override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
// …
}
override fun onOpen(db: SupportSQLiteDatabase) {
// …
}
}
توابع @RawQuery
DAO را تبدیل کنید
توابع حاشیه نویسی شده با @RawQuery
که برای پلتفرم های غیر اندرویدی کامپایل شده اند باید به جای SupportSQLiteQuery
پارامتری از نوع RoomRawQuery
را اعلام کنند.
چند پلتفرم کاتلین
پرس و جو خام را تعریف کنید
@Dao
interface TodoDao {
@RawQuery
suspend fun getTodos(query: RoomRawQuery): List<TodoEntity>
}
سپس می توان از RoomRawQuery
برای ایجاد یک پرس و جو در زمان اجرا استفاده کرد:
suspend fun AppDatabase.getTodosWithLowercaseTitle(title: String): List<TodoEntity> {
val query = RoomRawQuery(
sql = "SELECT * FROM TodoEntity WHERE title = ?",
onBindStatement = {
it.bindText(1, title.lowercase())
}
)
return todoDao().getTodos(query)
}
فقط اندروید
پرس و جو خام را تعریف کنید
@Dao
interface TodoDao {
@RawQuery
suspend fun getTodos(query: SupportSQLiteQuery): List<TodoEntity>
}
سپس می توان از SimpleSQLiteQuery
برای ایجاد یک پرس و جو در زمان اجرا استفاده کرد:
suspend fun AndroidOnlyDao.getTodosWithLowercaseTitle(title: String): List<TodoEntity> {
val query = SimpleSQLiteQuery(
query = "SELECT * FROM TodoEntity WHERE title = ?",
bindArgs = arrayOf(title.lowercase())
)
return getTodos(query)
}
تبدیل توابع مسدود کننده DAO
اتاق از کتابخانه ناهمزمان kotlinx.coroutines
با ویژگیهای غنی که Kotlin برای چندین پلتفرم ارائه میکند، بهره میبرد. برای عملکرد بهینه، توابع suspend
برای DAOهای کامپایل شده در یک پروژه KMP، به استثنای DAOهایی که در androidMain
برای حفظ سازگاری با پایگاه کد موجود پیاده سازی شده اند، اعمال می شوند. هنگام استفاده از Room for KMP، همه توابع DAO که برای پلتفرمهای غیر اندرویدی کامپایل شدهاند، باید توابع suspend
باشند.
چند پلتفرم کاتلین
تعلیق پرس و جوها
@Query("SELECT * FROM Todo")
suspend fun getAllTodos(): List<Todo>
تعلیق معاملات
@Transaction
suspend fun transaction() { … }
فقط اندروید
مسدود کردن پرس و جوها
@Query("SELECT * FROM Todo")
fun getAllTodos(): List<Todo>
مسدود کردن معاملات
@Transaction
fun blockingTransaction() { … }
انواع راکتیو را به Flow تبدیل کنید
لازم نیست همه توابع DAO توابع تعلیق باشند. توابع DAO که انواع واکنشی مانند LiveData
یا RxJava's Flowable
برمی گرداند، نباید به توابع تعلیق تبدیل شوند. با این حال، برخی از انواع مانند LiveData
با KMP سازگار نیستند. توابع DAO با انواع برگشتی واکنشی باید به جریان های معمولی منتقل شوند.
چند پلتفرم کاتلین
انواع واکنشی Flows
@Query("SELECT * FROM Todo")
fun getTodosFlow(): Flow<List<Todo>>
فقط اندروید
انواع واکنشی مانند LiveData
یا RxJava's Flowable
@Query("SELECT * FROM Todo")
fun getTodosLiveData(): LiveData<List<Todo>>
تبدیل API های تراکنش
API های تراکنش پایگاه داده برای اتاق KMP می توانند بین تراکنش های نوشتن ( useWriterConnection
) و خواندن ( useReaderConnection
) تفاوت قائل شوند.
چند پلتفرم کاتلین
val database: RoomDatabase = …
database.useWriterConnection { transactor ->
transactor.immediateTransaction {
// perform database operations in transaction
}
}
فقط اندروید
val database: RoomDatabase = …
database.withTransaction {
// perform database operations in transaction
}
معاملات را بنویسید
از تراکنش های نوشتن استفاده کنید تا مطمئن شوید که پرس و جوهای متعدد داده ها را به صورت اتمی می نویسند تا خوانندگان بتوانند به طور مداوم به داده ها دسترسی داشته باشند. می توانید این کار را با استفاده از useWriterConnection
با هر یک از سه نوع تراکنش انجام دهید:
immediateTransaction
: در حالت Write-Ahead Logging (WAL) (پیشفرض)، این نوع تراکنش هنگام شروع قفل میشود، اما خوانندگان میتوانند به خواندن ادامه دهند. این انتخاب برای اکثر موارد ارجح است.deferredTransaction
: تراکنش تا اولین دستور نوشتن قفل نمی شود. از این نوع تراکنش به عنوان یک بهینه سازی زمانی استفاده کنید که مطمئن نیستید که آیا عملیات نوشتن در تراکنش مورد نیاز است یا خیر. به عنوان مثال، اگر تراکنش را برای حذف آهنگ ها از لیست پخشی که فقط نام لیست پخش داده شده است شروع کنید و لیست پخش وجود نداشته باشد، در این صورت عملیات نوشتن (حذف) مورد نیاز نیست.exclusiveTransaction
: این حالت شبیه بهimmediateTransaction
در حالت WAL عمل می کند. در حالت های دیگر ژورنال، از خواندن سایر اتصالات پایگاه داده در حین انجام تراکنش جلوگیری می کند.
معاملات را بخوانید
از تراکنش های خواندنی برای خواندن مداوم از پایگاه داده چندین بار استفاده کنید. به عنوان مثال، وقتی دو یا چند پرس و جو جداگانه دارید و از بند JOIN
استفاده نمی کنید. فقط تراکنش های معوق در اتصالات خواننده مجاز هستند. تلاش برای شروع یک تراکنش فوری یا انحصاری در یک اتصال خواننده یک استثنا ایجاد می کند، زیرا اینها عملیات "نوشتن" در نظر گرفته می شوند.
val database: RoomDatabase = …
database.useReaderConnection { transactor ->
transactor.deferredTransaction {
// perform database operations in transaction
}
}
در چند پلتفرم Kotlin موجود نیست
برخی از APIهایی که برای اندروید در دسترس بودند در کاتلین چند پلتفرم در دسترس نیستند.
درخواست پاسخ به تماس
APIهای زیر برای پیکربندی پاسخهای درخواستی به طور مشترک در دسترس نیستند و بنابراین در پلتفرمهایی غیر از Android در دسترس نیستند.
-
RoomDatabase.Builder.setQueryCallback
-
RoomDatabase.QueryCallback
ما در نظر داریم در نسخه آینده اتاق، پشتیبانی از پاسخ به درخواست را اضافه کنیم.
API برای پیکربندی RoomDatabase
با درخواست پاسخ به تماس RoomDatabase.Builder.setQueryCallback
همراه با واسط callback RoomDatabase.QueryCallback
به طور مشترک در دسترس نیستند و بنابراین در پلتفرم های دیگر غیر از Android در دسترس نیستند.
بسته شدن خودکار پایگاه داده
API برای فعال کردن بسته شدن خودکار پس از مهلت زمانی، RoomDatabase.Builder.setAutoCloseTimeout
، فقط در Android در دسترس است و در سایر سیستم عامل ها در دسترس نیست.
پایگاه داده پیش بسته
APIهای زیر برای ایجاد یک RoomDatabase
با استفاده از یک پایگاه داده موجود (یعنی یک پایگاه داده از پیش بسته بندی شده) مشترک نیستند و بنابراین در پلتفرم های دیگر غیر از Android در دسترس نیستند. این APIها عبارتند از:
-
RoomDatabase.Builder.createFromAsset
-
RoomDatabase.Builder.createFromFile
-
RoomDatabase.Builder.createFromInputStream
-
RoomDatabase.PrepackagedDatabaseCallback
ما قصد داریم در نسخه بعدی Room پشتیبانی از پایگاه های داده از پیش بسته بندی شده را اضافه کنیم.
بی اعتباری چند موردی
API برای فعال کردن باطلسازی چند نمونهای، RoomDatabase.Builder.enableMultiInstanceInvalidation
فقط در Android در دسترس است و در پلتفرمهای رایج یا دیگر در دسترس نیست.
برای شما توصیه می شود
- توجه: وقتی جاوا اسکریپت خاموش است، متن پیوند نمایش داده می شود
- برنامههای موجود را به Room KMP Codelab منتقل کنید
- با KMP Codelab شروع کنید
- داده ها را در پایگاه داده محلی با Room ذخیره کنید