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

کتابخانه تداوم اتاق یک لایه انتزاعی را بر روی SQLite فراهم می کند تا در عین استفاده از قدرت کامل SQLite، امکان دسترسی قوی تر به پایگاه داده را فراهم کند. این صفحه بر روی استفاده از اتاق در پروژه‌های چند پلتفرمی Kotlin (KMP) تمرکز دارد. برای اطلاعات بیشتر در مورد استفاده از اتاق، به ذخیره داده ها در پایگاه داده محلی با استفاده از اتاق یا نمونه های رسمی ما مراجعه کنید.

راه اندازی وابستگی ها

نسخه فعلی Room که از KMP پشتیبانی می کند 2.7.0-alpha01 یا بالاتر است.

برای راه‌اندازی Room در پروژه KMP خود، وابستگی‌های مصنوعات را در فایل build.gradle.kts برای ماژول خود اضافه کنید:

  • androidx.room:room-gradle-plugin - پلاگین Gradle برای پیکربندی طرحواره های اتاق
  • androidx.room:room-compiler - پردازنده KSP که کد تولید می کند
  • androidx.room:room-runtime - بخش زمان اجرا از کتابخانه
  • androidx.sqlite:sqlite-bundled - (اختیاری) کتابخانه SQLite همراه

علاوه بر این، باید درایور SQLite اتاق را پیکربندی کنید. این درایورها بر اساس پلتفرم هدف متفاوت هستند. برای توضیحات پیاده سازی درایورهای موجود ، پیاده سازی درایور را ببینید.

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

تعریف کلاس های پایگاه داده

شما باید یک کلاس پایگاه داده مشروح شده با @Database همراه با DAO ها و موجودیت ها در داخل مجموعه منبع مشترک ماژول KMP مشترک خود ایجاد کنید. قرار دادن این کلاس ها در منابع مشترک به آنها اجازه می دهد تا در تمام پلتفرم های هدف به اشتراک گذاشته شوند.

هنگامی که یک شیء expect با رابط RoomDatabaseConstructor اعلام می کنید، کامپایلر Room پیاده سازی های actual را ایجاد می کند. Android Studio ممکن است اخطاری صادر کند "Expected object 'AppDatabaseConstructor' has no actual declaration in module" . می توانید اخطار را با @Suppress("NO_ACTUAL_FOR_EXPECT") سرکوب کنید.

// 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("NO_ACTUAL_FOR_EXPECT")
expect object AppDatabaseConstructor : RoomDatabaseConstructor<AppDatabase> {
    override fun initialize(): AppDatabase
}

@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>>
}

@Entity
data class TodoEntity(
  @PrimaryKey(autoGenerate = true) val id: Long = 0,
  val title: String,
  val content: String
)

توجه داشته باشید که می‌توانید به صورت اختیاری از اعلان‌های واقعی / انتظاری برای ایجاد پیاده‌سازی‌های اتاق مخصوص پلتفرم استفاده کنید. برای مثال، می‌توانید یک DAO مخصوص پلتفرم را اضافه کنید که در کدهای رایج با استفاده از expect تعریف شده است و سپس تعاریف actual را با پرس‌وجوهای اضافی در مجموعه‌های منبع خاص پلتفرم مشخص کنید.

ایجاد سازنده پایگاه داده

برای نمونه سازی Room در هر پلتفرم باید یک سازنده پایگاه داده تعریف کنید. این تنها بخشی از API است که به دلیل تفاوت در API های سیستم فایل، باید در مجموعه های منبع خاص پلت فرم باشد. به عنوان مثال، در Android، مکان پایگاه داده معمولاً از طریق Context.getDatabasePath() API به دست می آید، در حالی که برای iOS، مکان پایگاه داده با استفاده از NSFileManager به دست می آید.

اندروید

برای ایجاد نمونه پایگاه داده، یک Context را به همراه مسیر پایگاه داده مشخص کنید.

// shared/src/androidMain/kotlin/Database.kt

fun getDatabaseBuilder(ctx: Context): RoomDatabase.Builder<AppDatabase> {
  val appContext = ctx.applicationContext
  val dbFile = appContext.getDatabasePath("my_room.db")
  return Room.databaseBuilder<AppDatabase>(
    context = appContext,
    name = dbFile.absolutePath
  )
}

iOS

برای ایجاد نمونه پایگاه داده، یک مسیر پایگاه داده با استفاده از NSFileManager ، که معمولاً در NSDocumentDirectory قرار دارد، ارائه دهید.

// shared/src/iosMain/kotlin/Database.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.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
      .addMigrations(MIGRATIONS)
      .fallbackToDestructiveMigrationOnDowngrade()
      .setDriver(BundledSQLiteDriver())
      .setQueryCoroutineContext(Dispatchers.IO)
      .build()
}

انتخاب SQLiteDriver

قطعه کد قبلی از BundledSQLiteDriver استفاده می کند. این درایور پیشنهادی است که شامل SQLite است که از منبع کامپایل شده است، که سازگارترین و به‌روزترین نسخه SQLite را در همه پلتفرم‌ها ارائه می‌دهد. اگر می‌خواهید از SQLite ارائه‌شده توسط سیستم‌عامل استفاده کنید، از setDriver API در مجموعه‌های منبع خاص پلتفرم که درایور مخصوص پلتفرم را مشخص می‌کنند، استفاده کنید. برای اندروید، می‌توانید از AndroidSQLiteDriver استفاده کنید، در حالی که برای iOS می‌توانید از NativeSQLiteDriver استفاده کنید. برای استفاده از NativeSQLiteDriver ، باید یک گزینه پیوند دهنده ارائه دهید تا برنامه 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")
        }
    }
}

تفاوت ها

Room در ابتدا به عنوان یک کتابخانه اندروید توسعه یافت و بعداً با تمرکز بر سازگاری API به KMP منتقل شد. نسخه KMP اتاق بین پلتفرم‌ها و با نسخه مخصوص اندروید تا حدودی متفاوت است. این تفاوت ها به شرح زیر فهرست شده و شرح داده شده است.

مسدود کردن توابع DAO

هنگام استفاده از Room for KMP، همه توابع DAO که برای پلتفرم‌های غیر اندرویدی کامپایل شده‌اند، باید توابع suspend شوند، به استثنای انواع برگشتی واکنش‌پذیر، مانند Flow .

// shared/src/commonMain/kotlin/MultiplatformDao.kt

@Dao
interface MultiplatformDao {
  // ERROR: Blocking function not valid for non-Android targets
  @Query("SELECT * FROM Entity")
  fun blockingQuery(): List<Entity>

  // OK
  @Query("SELECT * FROM Entity")
  suspend fun query(): List<Entity>

  // OK
  @Query("SELECT * FROM Entity")
  fun queryFlow(): Flow<List<Entity>>

  // ERROR: Blocking function not valid for non-Android targets
  @Transaction
  fun blockingTransaction() { // … }

  // OK
  @Transaction
  suspend fun transaction() { // … }
}

اتاق از کتابخانه ناهمزمان kotlinx.coroutines با ویژگی‌های غنی که Kotlin برای چندین پلتفرم ارائه می‌کند، بهره می‌برد. برای عملکرد بهینه، توابع suspend برای DAO های کامپایل شده در یک پروژه KMP، به استثنای DAO های خاص اندروید برای حفظ سازگاری با پایگاه کد موجود، اعمال می شوند.

تفاوت ویژگی ها با KMP

این بخش چگونگی تفاوت ویژگی‌ها را بین نسخه‌های اتاق سیستم عامل KMP و Android توضیح می‌دهد.

توابع @RawQuery DAO

توابع حاشیه نویسی شده با @RawQuery که برای پلتفرم های غیر اندرویدی کامپایل شده اند باید به جای SupportSQLiteQuery پارامتری از نوع RoomRawQuery را اعلام کنند.

@Dao
interface TodoDao {
  @RawQuery
  suspend fun getTodos(query RoomRawQuery): List<TodoEntity>
}

سپس می توان از RoomRawQuery برای ایجاد یک پرس و جو در زمان اجرا استفاده کرد:

suspend fun getTodosWithLowercaseTitle(title: String): List<TodoEntity> {
  val query = RoomRawQuery(
    sql = "SELECT * FROM TodoEntity WHERE title = ?"
    onBindStatement = {
      it.bindText(1, title.lowercase())
    }
  )
  return todosDao.getTodos(query)
}

درخواست پاسخ به تماس

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