रूम (Kotlin मल्टीप्लैटफ़ॉर्म)

रूम परसिस्टेंस लाइब्रेरी, SQLite को लेकर एक ऐब्स्ट्रैक्शन लेयर उपलब्ध कराती है, ताकि डेटाबेस को ज़्यादा अच्छे से ऐक्सेस किया जा सके. इस पेज पर, Kotlin Multiplatform (KMP) प्रोजेक्ट में Room का इस्तेमाल करने के बारे में बताया गया है. Room का इस्तेमाल करने के बारे में ज़्यादा जानकारी के लिए, Room का इस्तेमाल करके, डेटा को किसी स्थानीय डेटाबेस में सेव करना लेख पढ़ें या हमारे आधिकारिक सैंपल देखें.

डिपेंडेंसी सेट अप करना

Room का मौजूदा वर्शन, 2.7.0-alpha01 या इसके बाद का वर्शन होना चाहिए, ताकि KMP का इस्तेमाल किया जा सके.

अपने KMP प्रोजेक्ट में Room को सेट अप करने के लिए, अपने मॉड्यूल के लिए build.gradle.kts फ़ाइल में आर्टफ़ैक्ट की डिपेंडेंसी जोड़ें:

  • androidx.room:room-gradle-plugin - Room स्कीमा को कॉन्फ़िगर करने के लिए Gradle प्लग इन
  • androidx.room:room-compiler - कोड जनरेट करने वाला KSP प्रोसेसर
  • androidx.room:room-runtime - लाइब्रेरी का रनटाइम हिस्सा
  • androidx.sqlite:sqlite-bundled - (ज़रूरी नहीं) बंडल की गई SQLite लाइब्रेरी

इसके अलावा, आपको Room के SQLite ड्राइवर को कॉन्फ़िगर करना होगा. टारगेट किए गए प्लैटफ़ॉर्म के हिसाब से, ये ड्राइवर अलग-अलग होते हैं. ड्राइवर लागू करने के तरीकों के बारे में जानने के लिए, ड्राइवर लागू करने के तरीके देखें.

सेटअप के बारे में ज़्यादा जानकारी के लिए, ये लेख पढ़ें:

डेटाबेस क्लास तय करना

आपको अपने शेयर किए गए KMP मॉड्यूल के सामान्य सोर्स सेट में, डीएओ और इकाइयों के साथ-साथ @Database के साथ एनोटेट की गई डेटाबेस क्लास बनानी होगी. इन कक्षाओं को सामान्य सोर्स में डालने से, उन्हें सभी टारगेट प्लैटफ़ॉर्म पर शेयर किया जा सकेगा.

इंटरफ़ेस RoomDatabaseConstructor के साथ expect ऑब्जेक्ट का एलान करने पर, 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
)

ध्यान दें कि प्लैटफ़ॉर्म के हिसाब से Room लागू करने के लिए, असल / उम्मीद के मुताबिक एलान का इस्तेमाल किया जा सकता है. हालांकि, ऐसा करना ज़रूरी नहीं है. उदाहरण के लिए, expect का इस्तेमाल करके, प्लैटफ़ॉर्म के हिसाब से DAO जोड़ा जा सकता है. इसके बाद, प्लैटफ़ॉर्म के हिसाब से सोर्स सेट में अतिरिक्त क्वेरी के साथ actual की परिभाषाएं बताई जा सकती हैं.

डेटाबेस बिल्डर बनाना

आपको हर प्लैटफ़ॉर्म पर Room को इंस्टैंशिएट करने के लिए, डेटाबेस बिल्डर तय करना होगा. फ़ाइल सिस्टम एपीआई में अंतर की वजह से, यह एपीआई का एकमात्र हिस्सा है जिसे प्लैटफ़ॉर्म के हिसाब से सोर्स सेट में होना ज़रूरी है. उदाहरण के लिए, Android में, डेटाबेस की जगह की जानकारी आम तौर पर Context.getDatabasePath() एपीआई से मिलती है. वहीं, 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)
}

जेवीएम (डेस्कटॉप)

डेटाबेस इंस्टेंस बनाने के लिए, Java या Kotlin एपीआई का इस्तेमाल करके डेटाबेस पाथ दें.

// 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,
    )
}

कोड का साइज़ कम करना और उसे अस्पष्ट बनाना

अगर प्रोजेक्ट को छोटा या गड़बड़ी वाला बनाया गया है, तो ProGuard का यह नियम शामिल करना ज़रूरी है, ताकि Room को डेटाबेस डेफ़िनिशन का जनरेट किया गया लागू करने का तरीका मिल सके:

-keep class * extends androidx.room.RoomDatabase { <init>(); }

डेटाबेस इंस्टैंशिएशन

प्लैटफ़ॉर्म के हिसाब से बने किसी कंस्ट्रक्टर से RoomDatabase.Builder पाने के बाद, बाकी Room डेटाबेस को सामान्य कोड में कॉन्फ़िगर किया जा सकता है. साथ ही, डेटाबेस को असली तौर पर इंस्टैंशिएट किया जा सकता है.

// 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 एपीआई का इस्तेमाल करें. इन सोर्स सेट में, प्लैटफ़ॉर्म के हिसाब से ड्राइवर की जानकारी होती है. Android के लिए, 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 को मूल रूप से Android लाइब्रेरी के तौर पर डेवलप किया गया था. बाद में, एपीआई के साथ काम करने की सुविधा पर फ़ोकस करने के लिए, इसे KMP पर माइग्रेट कर दिया गया. Room का KMP वर्शन, प्लैटफ़ॉर्म और Android के वर्शन के हिसाब से अलग-अलग होता है. इन दोनों के बीच के फ़र्क़ की सूची यहां दी गई है. साथ ही, इनके बारे में यहां बताया गया है.

डीएओ फ़ंक्शन ब्लॉक करना

KMP के लिए Room का इस्तेमाल करते समय, Android के अलावा अन्य प्लैटफ़ॉर्म के लिए कॉम्पाइल किए गए सभी 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() { // … }
}

Room को कई सुविधाओं वाली असाइनोक्रोनस kotlinx.coroutines लाइब्रेरी का फ़ायदा मिलता है. यह लाइब्रेरी, Kotlin कई प्लैटफ़ॉर्म के लिए उपलब्ध कराता है. बेहतर तरीके से काम करने के लिए, suspend फ़ंक्शन को KMP प्रोजेक्ट में कंपाइल किए गए DAO के लिए लागू किया जाता है. हालांकि, Android के लिए खास तौर पर बनाए गए DAO के लिए ऐसा नहीं किया जाता, ताकि मौजूदा कोडबेस के साथ पुराने सिस्टम के साथ काम करने की सुविधा बनी रहे.

KMP के साथ सुविधाओं में अंतर

इस सेक्शन में बताया गया है कि Room के KMP और Android प्लैटफ़ॉर्म के वर्शन के बीच सुविधाओं में क्या अंतर है.

@RawQuery डीएओ फ़ंक्शन

@RawQuery के साथ एनोटेट किए गए ऐसे फ़ंक्शन जिन्हें Android के अलावा किसी दूसरे प्लैटफ़ॉर्म के लिए कॉम्पाइल किया गया है, उनके लिए 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)
}

क्वेरी कॉलबैक

क्वेरी कॉलबैक को कॉन्फ़िगर करने के लिए, यहां दिए गए एपीआई सामान्य तौर पर उपलब्ध नहीं हैं. इसलिए, ये Android के अलावा अन्य प्लैटफ़ॉर्म पर भी उपलब्ध नहीं हैं.

  • RoomDatabase.Builder.setQueryCallback
  • RoomDatabase.QueryCallback

हम आने वाले समय में, Room के नए वर्शन में क्वेरी कॉलबैक की सुविधा जोड़ने जा रहे हैं.

क्वेरी कॉलबैक RoomDatabase.Builder.setQueryCallback के साथ RoomDatabase को कॉन्फ़िगर करने के लिए एपीआई, कॉलबैक इंटरफ़ेस RoomDatabase.QueryCallback के साथ आम तौर पर उपलब्ध नहीं है. इसलिए, यह Android के अलावा अन्य प्लैटफ़ॉर्म पर उपलब्ध नहीं है.

अपने-आप बंद होने वाला डेटाबेस

टाइम आउट के बाद अपने-आप बंद होने की सुविधा चालू करने वाला एपीआई, RoomDatabase.Builder.setAutoCloseTimeout सिर्फ़ Android पर उपलब्ध है. यह सुविधा अन्य प्लैटफ़ॉर्म पर उपलब्ध नहीं है.

पहले से पैकेज किया गया डेटाबेस

किसी मौजूदा डेटाबेस (यानी पहले से पैकेज किया गया डेटाबेस) का इस्तेमाल करके RoomDatabase बनाने के लिए, ये एपीआई आम तौर पर उपलब्ध नहीं होते. इसलिए, ये Android के अलावा अन्य प्लैटफ़ॉर्म पर भी उपलब्ध नहीं होते. ये एपीआई हैं:

  • RoomDatabase.Builder.createFromAsset
  • RoomDatabase.Builder.createFromFile
  • RoomDatabase.Builder.createFromInputStream
  • RoomDatabase.PrepackagedDatabaseCallback

हम आने वाले समय में, Room के नए वर्शन में पहले से पैकेज किए गए डेटाबेस के लिए सहायता जोड़ने जा रहे हैं.

एक से ज़्यादा इंस्टेंस अमान्य करना

एक से ज़्यादा इंस्टेंस अमान्य करने की सुविधा चालू करने वाला एपीआई, RoomDatabase.Builder.enableMultiInstanceInvalidation सिर्फ़ Android पर उपलब्ध है. यह सामान्य या अन्य प्लैटफ़ॉर्म पर उपलब्ध नहीं है.