ห้อง (Kotlin Multiplatform)

ไลบรารีการคงอยู่ของห้องจะมีเลเยอร์ Abstraction เหนือ SQLite เพื่อให้ เพื่อการเข้าถึงฐานข้อมูลที่มีประสิทธิภาพมากขึ้นพร้อมกับใช้ประโยชน์จาก SQLite อย่างเต็มที่ ช่วงเวลานี้ มุ่งเน้นไปที่การใช้ Room ในโปรเจ็กต์ Kotlin Multiplatform (KMP) สำหรับข้อมูลเพิ่มเติม ข้อมูลเกี่ยวกับการใช้ห้องแชทที่หัวข้อบันทึกข้อมูลในฐานข้อมูลในเครื่องโดยใช้ห้องแชท หรือตัวอย่างอย่างเป็นทางการของเรา

การตั้งค่าทรัพยากร Dependency

ห้องแชทเวอร์ชันปัจจุบันที่รองรับ KMP คือ 2.7.0-alpha01 ขึ้นไป

หากต้องการตั้งค่าห้องในโปรเจ็กต์ KMP ให้เพิ่มทรัพยากร Dependency สำหรับอาร์ติแฟกต์ใน build.gradle.kts ไฟล์สำหรับโมดูลของคุณ:

  • androidx.room:room-gradle-plugin - ปลั๊กอิน Gradle สำหรับกำหนดค่าสคีมาของ Room
  • 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 พร้อมด้วยคำค้นหาเพิ่มเติมเฉพาะแพลตฟอร์ม ชุดแหล่งที่มา

การสร้างเครื่องมือสร้างฐานข้อมูล

คุณต้องกำหนดเครื่องมือสร้างฐานข้อมูลเพื่อสร้างอินสแตนซ์ห้องแชทในแต่ละแพลตฟอร์ม ช่วงเวลานี้ เป็นเพียงส่วนเดียวของ API ที่จำเป็นต้องอยู่ในแหล่งที่มาเฉพาะแพลตฟอร์ม เนื่องจาก API ระบบไฟล์แตกต่างกัน ตัวอย่างเช่น ใน Android ตำแหน่งฐานข้อมูลมักจะได้มาจาก Context.getDatabasePath() API ในขณะที่สำหรับ iOS ตำแหน่งฐานข้อมูลคือ จะได้รับโดยใช้ NSFileManager

Android

หากต้องการสร้างอินสแตนซ์ฐานข้อมูล ให้ระบุบริบทพร้อมกับฐานข้อมูล เส้นทาง

// 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 ที่ระบบปฏิบัติการมีให้ ให้ใช้ API ของ setDriver เฉพาะแพลตฟอร์ม ชุดแหล่งที่มาที่ระบุไดรเวอร์เฉพาะแพลตฟอร์ม สำหรับ Android คุณสามารถใช้ AndroidSQLiteDriver แต่สำหรับ iOS คุณใช้ NativeSQLiteDriver ได้ ถึง ใช้ NativeSQLiteDriver คุณจะต้องระบุตัวเลือก Linker เพื่อให้ 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")
        }
    }
}

ความแตกต่าง

เดิมทีห้องแชทได้รับการพัฒนาให้เป็นไลบรารี Android และต่อมาได้ย้ายข้อมูลไปยัง KMP ที่มุ่งเน้นความเข้ากันได้ของ API ห้องแชทเวอร์ชัน KMP จะแตกต่างกันบ้าง ระหว่างแพลตฟอร์มต่างๆ และจากเวอร์ชันเฉพาะสำหรับ Android ความแตกต่างเหล่านี้ แสดงไว้และอธิบายไว้ดังนี้

การบล็อกฟังก์ชัน DAO

เมื่อใช้ Room สำหรับ KMP ฟังก์ชัน DAO ทั้งหมดที่คอมไพล์สำหรับแพลตฟอร์มที่ไม่ใช่ Android ต้องเป็นฟังก์ชัน 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 เฉพาะ Android เพื่อรักษาความเข้ากันได้แบบย้อนหลังกับ ฐานของโค้ด

ความแตกต่างของฟีเจอร์กับ KMP

ส่วนนี้จะอธิบายความแตกต่างของฟีเจอร์ต่างๆ ระหว่าง KMP กับแพลตฟอร์ม Android เวอร์ชันของห้องแชท

ฟังก์ชัน @RawQuery DAO

ฟังก์ชันที่มีคำอธิบายประกอบด้วย @RawQuery ซึ่งคอมไพล์สำหรับแพลตฟอร์มที่ไม่ใช่ Android จะต้องประกาศพารามิเตอร์ประเภท RoomRawQuery แทน SupportSQLiteQuery

@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 ต่อไปนี้สำหรับการกำหนดค่า Callback ของคำค้นหาไม่พร้อมใช้งานร่วมกัน ดังนั้นจึงไม่พร้อมใช้งานในแพลตฟอร์มอื่นที่ไม่ใช่ Android

  • RoomDatabase.Builder.setQueryCallback
  • RoomDatabase.QueryCallback

เราตั้งใจที่จะเพิ่มการรองรับการติดต่อกลับสำหรับคำถามใน Room เวอร์ชันในอนาคต

API ที่จะกำหนดค่า RoomDatabase ที่มี Callback สืบค้น 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

เราตั้งใจที่จะเพิ่มการสนับสนุนสำหรับฐานข้อมูลที่จัดเตรียมไว้ล่วงหน้าใน ห้องแชท

การเอาข้อมูลเก่าในอินสแตนซ์แบบหลายอินสแตนซ์

API สำหรับเปิดใช้การทำให้หลายอินสแตนซ์ใช้งานไม่ได้ RoomDatabase.Builder.enableMultiInstanceInvalidation มีเฉพาะใน Android และไม่พร้อมใช้งานในแพลตฟอร์มทั่วไปหรือแพลตฟอร์มอื่นๆ