اتاق (کوتلین چند پلتفرم)

کتابخانه تداوم اتاق یک لایه انتزاعی را بر روی 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 در مجموعه‌های منبع خاص پلتفرم که درایور مخصوص پلتفرم را مشخص می‌کنند، استفاده کنید. برای توضیحات پیاده سازی درایورهای موجود، پیاده سازی درایور را ببینید. می توانید از یکی از موارد زیر استفاده کنید:

برای استفاده از 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 در دسترس است و در پلتفرم‌های رایج یا دیگر در دسترس نیست.

{% کلمه به کلمه %} {% آخر کلمه %} {% کلمه به کلمه %} {% آخر کلمه %}