রুম (কোটলিন মাল্টিপ্ল্যাটফর্ম)

রুম পারসিসটেন্স লাইব্রেরি SQLite এর উপর একটি অ্যাবস্ট্রাকশন লেয়ার প্রদান করে যাতে SQLite এর পূর্ণ শক্তি ব্যবহার করার সময় আরও শক্তিশালী ডাটাবেস অ্যাক্সেসের অনুমতি দেওয়া হয়। এই পৃষ্ঠাটি কোটলিন মাল্টিপ্ল্যাটফর্ম (KMP) প্রকল্পে রুম ব্যবহার করার উপর দৃষ্টি নিবদ্ধ করে। রুম ব্যবহার করার বিষয়ে আরও তথ্যের জন্য, রুম বা আমাদের অফিসিয়াল নমুনা ব্যবহার করে একটি স্থানীয় ডাটাবেসে ডেটা সংরক্ষণ করুন দেখুন।

নির্ভরতা সেট আপ করা হচ্ছে

রুমের বর্তমান সংস্করণ যা KMP সমর্থন করে তা হল 2.7.0-alpha01 বা উচ্চতর।

আপনার KMP প্রোজেক্টে রুম সেটআপ করতে, আপনার মডিউলের জন্য build.gradle.kts ফাইলে আর্টিফ্যাক্টগুলির জন্য নির্ভরতা যোগ করুন:

  • androidx.room:room-gradle-plugin - রুম স্কিমা কনফিগার করার জন্য গ্রেডল প্লাগইন
  • androidx.room:room-compiler - KSP প্রসেসর যা কোড তৈরি করে
  • androidx.room:room-runtime - লাইব্রেরির রানটাইম অংশ
  • androidx.sqlite:sqlite-bundled - (ঐচ্ছিক) বান্ডিল SQLite লাইব্রেরি

অতিরিক্তভাবে আপনাকে রুমের SQLite ড্রাইভার কনফিগার করতে হবে। এই ড্রাইভারগুলি লক্ষ্য প্ল্যাটফর্মের উপর ভিত্তি করে পৃথক। উপলব্ধ ড্রাইভার বাস্তবায়নের বর্ণনার জন্য ড্রাইভার বাস্তবায়ন দেখুন।

অতিরিক্ত সেটআপ তথ্যের জন্য, নিম্নলিখিত দেখুন:

ডাটাবেস ক্লাস সংজ্ঞায়িত করা

আপনার শেয়ার করা KMP মডিউলের সাধারণ উৎস সেটের ভিতরে DAO এবং সত্তা সহ @Database এর সাথে টীকাযুক্ত একটি ডাটাবেস ক্লাস তৈরি করতে হবে। সাধারণ উত্সগুলিতে এই ক্লাসগুলি স্থাপন করা তাদের সমস্ত লক্ষ্য প্ল্যাটফর্ম জুড়ে ভাগ করার অনুমতি দেবে৷

আপনি যখন RoomDatabaseConstructor ইন্টারফেসের সাথে একটি expect বস্তু ঘোষণা করেন, তখন রুম কম্পাইলার actual বাস্তবায়ন তৈরি করে। অ্যান্ড্রয়েড স্টুডিও একটি সতর্কতা জারি করতে পারে "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-এর পার্থক্যের কারণে প্ল্যাটফর্ম-নির্দিষ্ট উৎস সেটে থাকা প্রয়োজন। উদাহরণস্বরূপ, অ্যান্ড্রয়েডে, ডাটাবেস অবস্থান সাধারণত Context.getDatabasePath() API এর মাধ্যমে প্রাপ্ত করা হয়, যখন iOS এর জন্য, ডাটাবেস অবস্থান NSFileManager ব্যবহার করে প্রাপ্ত করা হয়।

অ্যান্ড্রয়েড

ডাটাবেস ইনস্ট্যান্স তৈরি করতে, ডাটাবেস পাথ সহ একটি প্রসঙ্গ উল্লেখ করুন।

// 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 (ডেস্কটপ)

ডাটাবেস ইনস্ট্যান্স তৈরি করতে, জাভা বা কোটলিন 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-এর সবচেয়ে সামঞ্জস্যপূর্ণ এবং আপ-টু-ডেট সংস্করণ প্রদান করে। আপনি যদি OS-প্রদত্ত SQLite ব্যবহার করতে চান, তাহলে প্ল্যাটফর্ম-নির্দিষ্ট উৎস সেটে setDriver API ব্যবহার করুন যা একটি প্ল্যাটফর্ম-নির্দিষ্ট ড্রাইভার নির্দিষ্ট করে। 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")
        }
    }
}

পার্থক্য

রুমটি মূলত একটি অ্যান্ড্রয়েড লাইব্রেরি হিসাবে তৈরি করা হয়েছিল এবং পরে API সামঞ্জস্যের উপর ফোকাস রেখে কেএমপিতে স্থানান্তরিত করা হয়েছিল। প্ল্যাটফর্ম এবং অ্যান্ড্রয়েড-নির্দিষ্ট সংস্করণ থেকে রুমের KMP সংস্করণ কিছুটা আলাদা। এই পার্থক্যগুলি নিম্নরূপ তালিকাভুক্ত এবং বর্ণনা করা হয়েছে।

DAO ফাংশন ব্লক করা

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 একাধিক প্ল্যাটফর্মের জন্য অফার করে। সর্বোত্তম কার্যকারিতার জন্য, বিদ্যমান কোডবেসের সাথে পিছনের সামঞ্জস্য বজায় রাখার জন্য অ্যান্ড্রয়েড নির্দিষ্ট DAO ব্যতীত একটি KMP প্রকল্পে কম্পাইল করা DAO-এর জন্য suspend ফাংশন প্রয়োগ করা হয়।

KMP এর সাথে বৈশিষ্ট্যের পার্থক্য

রুমের KMP এবং Android প্ল্যাটফর্ম সংস্করণগুলির মধ্যে বৈশিষ্ট্যগুলি কীভাবে আলাদা তা এই বিভাগে বর্ণনা করে৷

@RawQuery DAO ফাংশন

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

কলব্যাক জিজ্ঞাসা করুন

ক্যোয়ারী কলব্যাক কনফিগার করার জন্য নিম্নলিখিত APIগুলি সাধারণভাবে উপলব্ধ নয় এবং এইভাবে Android ছাড়া অন্য প্ল্যাটফর্মগুলিতে অনুপলব্ধ৷

  • RoomDatabase.Builder.setQueryCallback
  • RoomDatabase.QueryCallback

আমরা রুমের ভবিষ্যত সংস্করণে কোয়েরি কলব্যাকের জন্য সমর্থন যোগ করতে চাই।

একটি ক্যোয়ারী কলব্যাক RoomDatabase.Builder.setQueryCallback সহ কলব্যাক ইন্টারফেস RoomDatabase.QueryCallback সহ একটি RoomDatabase কনফিগার করার API সাধারণভাবে উপলব্ধ নয় এবং এইভাবে Android ছাড়া অন্য প্ল্যাটফর্মে উপলব্ধ নয়৷

অটো ক্লোজিং ডাটাবেস

টাইমআউটের পরে স্বয়ংক্রিয়ভাবে বন্ধ করার জন্য API, RoomDatabase.Builder.setAutoCloseTimeout , শুধুমাত্র Android এ উপলব্ধ এবং অন্যান্য প্ল্যাটফর্মগুলিতে উপলব্ধ নয়৷

প্রাক-প্যাকেজ ডাটাবেস

একটি বিদ্যমান ডাটাবেস (যেমন একটি প্রাক-প্যাকেজ করা ডাটাবেস) ব্যবহার করে একটি RoomDatabase তৈরি করার জন্য নিম্নলিখিত APIগুলি সাধারণভাবে উপলব্ধ নয় এবং তাই অ্যান্ড্রয়েড ছাড়া অন্য প্ল্যাটফর্মগুলিতে উপলব্ধ নয়৷ এই APIগুলি হল:

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

আমরা রুমের ভবিষ্যত সংস্করণে প্রাক-প্যাকেজ করা ডাটাবেসের জন্য সমর্থন যোগ করতে চাই।

মাল্টি-ইনস্ট্যান্স অবৈধকরণ

মাল্টি-ইনস্ট্যান্স অবৈধকরণ সক্ষম করার API, RoomDatabase.Builder.enableMultiInstanceInvalidation শুধুমাত্র Android এ উপলব্ধ এবং সাধারণ বা অন্যান্য প্ল্যাটফর্মে উপলব্ধ নয়৷