Room 2.x থেকে Room 3.0-তে মাইগ্রেট করা

Room 3.0 হল একটি প্রধান ভার্সন আপডেট যা লাইব্রেরিকে Kotlin-ফার্স্ট হিসেবে ট্রানজিশন করে। এটি Kotlin মাল্টিপ্ল্যাটফর্ম (KMP) সাপোর্ট করে, Kotlin সিম্বল প্রসেসিং (KSP) প্রয়োজন হয় এবং অ্যাসিঙ্ক্রোনাস অপারেশনের জন্য কোরুটিন এনফোর্স করে।

আগে থেকে থাকা Room 2.x অ্যাপ ও ট্রানজিটিভ ডিপেন্ডেন্সির সাথে মানানসই হওয়ার সমস্যা এড়াতে, Room 3.0 নতুন প্যাকেজে থাকে: androidx.room3।

এই নির্দেশিকায় আপনার আগে থেকে থাকা Room 2.x ইমপ্লিমেন্টেশন Room 3.0-এ মাইগ্রেট করার জন্য প্রয়োজনীয় ধাপগুলি উল্লেখ করা হয়েছে।

Room 3.0-তে হওয়া মূল পরিবর্তন

মাইগ্রেশন শুরু করার আগে, প্রধান পার্থক্যগুলি সম্পর্কে জেনে নিন:

  • নতুন প্যাকেজ ও আর্টিফ্যাক্ট: সব ক্লাস androidx.room3-এ থাকে। আর্টিফ্যাক্ট room3 প্রিফিক্স ব্যবহার করে, যেমন androidx.room3:room3-runtime।
  • শুধু Kotlin ও KSP: Room 3.0-এ Java কোড জেনারেশন কাজ করে না। KAPT বা Java অ্যানোটেশন প্রসেসরের পরিবর্তে KSP ব্যবহার করুন। Room 3.0 এখনও ইনপুট হিসেবে Java সোর্স সাপোর্ট করে।
  • প্রথমে Coroutines: DAO ফাংশন অবশ্যই suspend ফাংশন হতে হবে, তবে অবজার্ভেবল টাইপ এর ব্যতিক্রম। CoroutineContext এক্সিকিউটরদের প্রতিস্থাপন করে।
  • SupportSQLite নেই: SQLiteDriverটি API ব্যাক রুম। Room, মূল API থেকে SupportSQLiteDatabase সরিয়ে দেয়।
  • API সংক্রান্ত পরিবর্তন: মাইগ্রেশন ও ডেটাবেস কলব্যাক SupportSQLiteDatabase-এর পরিবর্তে SQLiteConnection ব্যবহার করে।
  • রিঅ্যাক্টিভ টাইপের জন্য কনভার্টার: RxJava, LiveData, Guava এবং Paging রিটার্ন টাইপের জন্য আপনাকে @DaoReturnTypeConverters রেজিস্টার করতে হবে।

আমরা দুটি আলাদা ধাপে মাইগ্রেট করার সাজেশন দিই: প্রথমে Room 2.x-এ আপনার কোডবেস প্রস্তুত ও আধুনিকীকরণ করা এবং তারপর Room 3.0-এ পরিবর্তন করা।


Room 2.x-এ প্রস্তুত ও আধুনিকীকরণ করা

Room 3.0-এ মাইগ্রেট করার আগে, আপনি Room 2.x-এর বর্তমান রিলিজ, যেমন Room 2.8-এ আপডেট করে আধুনিকীকরণের বেশিরভাগ কাজ করতে পারবেন। Room 2.8, Kotlin Multiplatform বা KMP-কে সাপোর্ট করে এবং এতে অনেক ড্রাইভার API অন্তর্ভুক্ত আছে যা Room 3.0 ব্যবহার করে।

Room 2.8 এবং তার পরবর্তী ভার্সনে আপডেট করুন

বর্তমান Room 2.x রিলিজ ব্যবহার করতে আপনার বিল্ড কনফিগারেশন আপডেট করুন:

[versions]
room2 = "2.8.4" # Use the current Room 2.8 version

[libraries]
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room2" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room2" }

KAPT থেকে KSP-তে মাইগ্রেট করা

Room 3.0-এ Java অ্যানোটেশন প্রসেসর বা KAPT কাজ করে না। আপনাকে অবশ্যই Kotlin Symbol Processing (KSP) ব্যবহার করতে হবে। Room 2.x-এ থাকাকালীন আপনি এই ট্রানজিশন করতে পারবেন।

  1. আপনার মডিউলের build.gradle.kts-এ, KSP প্লাগ-ইন প্রয়োগ করুন:

    plugins {
        id("com.google.devtools.ksp") version "<ksp_version>"
    }
    

    KSP ভার্সনটি আপনার Kotlin ভার্সনের সাথে মানানসই কিনা তা নিশ্চিত করুন।

  2. রুম কম্পাইলারের জন্য kapt বা annotationProcessor-এর পরিবর্তে ksp ব্যবহার করুন

    dependencies {
        implementation(libs.androidx.room.runtime)
        ksp(libs.androidx.room.compiler)
    }
    

করুটিন গ্রহণ করুন

অ্যাসিঙ্ক্রোনাস অপারেশনের জন্য Room 3.0-এ কোরুটিন প্রয়োজন।

  • আপনার DAO আপডেট করুন: তারা যদি কোনও পর্যবেক্ষণযোগ্য প্রতিক্রিয়াশীল ধরন ফেরত না দেয়, যেমন Flow বা RxJava ধরন, তাহলে সব DAO ফাংশন অবশ্যই suspend ফাংশন হতে হবে।
// Before (Blocking)
@Dao
interface UserDao {
    @Query("SELECT * FROM User")
    fun getAll(): List<User>
}

// After (Suspend)
@Dao
interface UserDao {
    @Query("SELECT * FROM User")
    suspend fun getAll(): List<User>
}
  • আপনি যদি ডেটাবেস অপারেশন পারফর্ম করার জন্য কাস্টম Executor-এর সাথে আপনার RoomDatabase কনফিগার করে থাকেন, তাহলে বিল্ডারে setQueryCoroutineContext ব্যবহার করে CoroutineContext-এ মাইগ্রেট করুন:
Room.databaseBuilder<AppDatabase>(context, "db")
    .setQueryCoroutineContext(Dispatchers.IO)
    .build()

ড্রাইভার API গ্রহণ করুন এবং SQLite এড়িয়ে চলুন

Room 3.0 সম্পূর্ণভাবে SQLiteDriver দ্বারা সমর্থিত এবং এর মূল API-তে আর SupportSQLiteDatabase কাজ করে না।

আপনার ডেটাবেস বিল্ডারে SQLiteDriver সেট করার জন্য setDriver-কে কল না করলে, Room 2.8 কম্প্যাটিবিলিটি মোডে কাজ করে যেখানে SQLite ও ড্রাইভার API, দুটিই কাজ করে। এই কম্প্যাটিবিলিটি মোড আপনাকে ড্রাইভার চালু করার আগে আপনার কোডবেসকে ধাপে ধাপে কনভার্ট করতে দেয়।

  • মাইগ্রেশন কনভার্ট করুন: SupportSQLiteDatabase-এর পরিবর্তে SQLiteConnection ব্যবহার করতে আপনার Migration এবং AutoMigrationSpec সাবক্লাস মাইগ্রেট করুন।
// Before (SupportSQLiteDatabase)
val MIGRATION_1_2 = object : Migration(1, 2) {
    override fun migrate(db: SupportSQLiteDatabase) {
        db.execSQL("ALTER TABLE User ADD COLUMN age INTEGER DEFAULT 0 NOT NULL")
    }
}

// After (SQLiteConnection - Room 2.8)
import androidx.sqlite.SQLiteConnection
import androidx.sqlite.execSQL

val MIGRATION_1_2 = object : Migration(1, 2) {
    override fun migrate(connection: SQLiteConnection) {
        connection.execSQL(
            "ALTER TABLE User ADD COLUMN age INTEGER DEFAULT 0 NOT NULL"
        )
    }
}
  • ডেটাবেস কলব্যাক কনভার্ট করুন: SQLiteConnection ব্যবহার করার জন্য RoomDatabase.Callback ইমপ্লিমেন্টেশন আপডেট করুন:
// Before (SupportSQLiteDatabase)
val callback = object : RoomDatabase.Callback() {
    override fun onCreate(db: SupportSQLiteDatabase) {
        // ...
    }
}

// After (SQLiteConnection - Room 2.8)
val callback = object : RoomDatabase.Callback() {
    override fun onCreate(connection: SQLiteConnection) {
        // ...
    }
}
  • @RawQuery DAO ফাংশন কনভার্ট করুন: @RawQuery-এর সাথে অ্যানোটেট করা ফাংশনের জন্য, SupportSQLiteQuery-এর পরিবর্তে RoomRawQuery ব্যবহার করুন:
// Before (SupportSQLiteQuery)
@Dao
interface UserDao {
    @RawQuery
    fun getUser(query: SupportSQLiteQuery): User
}

// After (RoomRawQuery)
@Dao
interface UserDao {
    @RawQuery
    suspend fun getUser(query: RoomRawQuery): User
}

আপনি রানটাইমে RoomRawQuery তৈরি করতে পারবেন:

val query = RoomRawQuery(
    sql = "SELECT * FROM User WHERE id = ?",
    onBindStatement = { statement ->
        statement.bindInt(1, userId)
    }
)
  • ট্রানজ্যাকশন API কনভার্ট করা: শুধু Android-এর জন্য withTransaction এবং runInTransaction ব্লক, useWriterConnection এবং immediateTransaction দিয়ে পরিবর্তন করুন:
// Before (withTransaction)
db.withTransaction {
    // perform database operations
}

// After (useWriterConnection - Room 2.8)
import androidx.room.useWriterConnection
import androidx.room.immediateTransaction

db.useWriterConnection { connection ->
    connection.immediateTransaction {
        // perform database operations
    }
}
  • SupportSQLiteDatabase সরাসরি ব্যবহার করা এড়িয়ে চলুন: আপনার কাছে যদি অনেক লিগ্যাসি কোড থাকে যার জন্য এখনও SupportSQLiteDatabase প্রয়োজন এবং আপনি এখনও এটি মাইগ্রেট করতে না পারেন, তাহলে androidx.room:room-sqlite-wrapper কম্প্যাটিবিলিটি আর্টিফ্যাক্ট ব্যবহার করুন:
dependencies {
    implementation("androidx.room:room-sqlite-wrapper:$roomVersion")
}

এবং তারপর, আপনার রুম ডেটাবেস ইনস্ট্যান্স থেকে SupportSQLiteDatabase পেতে getSupportWrapper ব্যবহার করুন:

import androidx.room.support.getSupportWrapper

val legacyDb: SupportSQLiteDatabase = roomDatabase.getSupportWrapper()
  • SQLite ড্রাইভার সেট করা: আপনি সব Room API ব্যবহার ড্রাইভার API-তে মাইগ্রেট করার পরে, আপনার RoomDatabase বিল্ডারে setDriver কল করে BundledSQLiteDriver বা AndroidSQLiteDriver-এর মতো ড্রাইভার কনফিগার করুন:
import androidx.sqlite.driver.bundled.BundledSQLiteDriver

val db = Room.databaseBuilder<AppDatabase>(context, "db")
    .setDriver(BundledSQLiteDriver())
    .build()

ফ্লো-ভিত্তিক ইনভ্যালিডেশন ট্র্যাকিং গ্রহণ করুন

Room 2.8-এ InvalidationTracker.createFlow API চালু করা হয়েছে। এই API ব্যবহার করুন যাতে Room 2.x-এ থাকাকালীন পুরনো InvalidationTracker.Observer ইমপ্লিমেন্টেশন থেকে মাইগ্রেট করা যায়। এটি Room 3.0-এর জন্য আপনার কোডবেস প্রস্তুত করে, যা Observer সম্পূর্ণভাবে সরিয়ে দেয়।

// Before (InvalidationTracker.Observer)
val observer = object : InvalidationTracker.Observer("User") {
    override fun onInvalidated(tables: Set<String>) {
        // reload user data
    }
}
db.invalidationTracker.addObserver(observer)

// After (createFlow - Room 2.8)
val userFlow = db.invalidationTracker.createFlow("User").map { _ ->
    userDao.getAllUsers()
}

Room 3.0-এ মাইগ্রেট করুন

Room 2.x-এ আপনার অ্যাপ্লিকেশন আধুনিকীকরণ করার পরে, Room 3.0-এ ট্রানজিশন করার মধ্যে ডিপেন্ডেন্সি, প্যাকেজ ইম্পোর্ট এবং ডেটাবেস কলব্যাক আপডেট করা অন্তর্ভুক্ত।

ডিপেন্ডেন্সি ও প্যাকেজ ইমপোর্ট আপডেট করুন

  • আপনার বিল্ড কনফিগারেশনে, androidx.room ডিপেন্ডেন্সি androidx.room3 দিয়ে পরিবর্তন করুন:
[versions]
room3 = "3.0.0" # Use the current Room 3.0 version

[libraries]
androidx-room3-runtime = { module = "androidx.room3:room3-runtime", version.ref = "room3" }
androidx-room3-compiler = { module = "androidx.room3:room3-compiler", version.ref = "room3" }
  • আপনার ডিপেন্ডেন্সি ব্লক আপডেট করুন:
dependencies {
    implementation(libs.androidx.room3.runtime)
    ksp(libs.androidx.room3.compiler)
}
  • আপনার প্যাকেজ ইম্পোর্ট আপডেট করুন। import androidx.room.*-এর জায়গায় import androidx.room3.* ব্যবহার করুন।

টাইপ কনভার্টার API আপডেট করুন

Room 3.0-এ টাইপ কনভার্টার API-এর নাম পরিবর্তন করা হয়েছে যাতে কলাম ভ্যালু কনভার্ট করার জন্য সেগুলির ব্যবহার স্পষ্ট করা যায় এবং DAO রিটার্ন টাইপ কনভার্টারের সাথে বিভ্রান্তি এড়ানো যায়।

আপনার কোডবেসে নিম্নলিখিত অ্যানোটেশন ও ফাংশন আপডেট করুন:

  • @TypeConverter-এর নাম পরিবর্তন করে @ColumnTypeConverter করুন।
  • @TypeConverters-এর নাম পরিবর্তন করে @ColumnTypeConverters করুন।
  • @ProvidedTypeConverter-এর নাম পরিবর্তন করে @ProvidedColumnTypeConverter করুন।
  • RoomDatabase.Builder.addTypeConverter-এর নাম পরিবর্তন করে addColumnTypeConverter করুন।

উদাহরণ:

// Before
@ProvidedTypeConverter
class Converters {
  @TypeConverter
  fun fromTimestamp(value: Long?): Date? = ...
}

@Database(entities = [User::class], version = 1)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase()

val db = Room.databaseBuilder<AppDatabase>(...)
  .addTypeConverter(convertersInstance)
  .build()
// After
import androidx.room3.ColumnTypeConverter
import androidx.room3.ColumnTypeConverters
import androidx.room3.ProvidedColumnTypeConverter

@ProvidedColumnTypeConverter
class Converters {
  @ColumnTypeConverter
  fun fromTimestamp(value: Long?): Date? = ...
}

@Database(entities = [User::class], version = 1)
@ColumnTypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase()

val db = Room.databaseBuilder<AppDatabase>(...)
  .addColumnTypeConverter(convertersInstance)
  .build()

ফাংশন সাসপেন্ড করতে কলব্যাক আপডেট করুন

Room 3.0-এ, ডেটাবেস কলব্যাক এবং মাইগ্রেশন SQLiteConnection এবং suspend ফাংশন ব্যবহার করে।

  • আপনার ম্যানুয়াল Migration ক্লাস আপডেট করুন:
import androidx.sqlite.SQLiteConnection
import androidx.sqlite.async.executeSQL

val MIGRATION_1_2 = object : Migration(1, 2) {
    override suspend fun migrate(connection: SQLiteConnection) {
        connection.executeSQL(
            "ALTER TABLE User ADD COLUMN age INTEGER DEFAULT 0 NOT NULL"
        )
    }
}
  • আপনার RoomDatabase.Callback প্রয়োগ আপডেট করুন:
val callback = object : RoomDatabase.Callback() {
    override suspend fun onCreate(connection: SQLiteConnection) {
        // ...
    }
}

DAO রিটার্ন টাইপ কনভার্টার রেজিস্টার করুন

Room 3.0-তে, RxJava, LiveData, Guava এবং Paging-এর মতো রিঅ্যাক্টিভ রিটার্ন টাইপের জন্য আপনাকে @DaoReturnTypeConverters ব্যবহার করে DAO রিটার্ন টাইপ কনভার্টার রেজিস্টার করতে হবে।

import androidx.room3.paging.PagingSourceDaoReturnTypeConverter

@Dao
@DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class)
interface UserDao {
    @Query("SELECT * FROM User")
    fun getAllPaginated(): PagingSource<Int, User>
}
  • পেজিং (PagingSource): androidx.room3:room3-paging আর্টিফ্যাক্ট থেকে PagingSourceDaoReturnTypeConverter রেজিস্টার করুন।
  • RxJava (Observable, Flowable, Single, Maybe, Completable): androidx.room3:room3-rxjava3 আর্টিফ্যাক্ট থেকে RxDaoReturnTypeConverters রেজিস্টার করুন।
  • Guava (ListenableFuture): androidx.room3:room3-guava আর্টিফ্যাক্ট থেকে GuavaDaoReturnTypeConverter রেজিস্টার করুন।
  • LiveData (LiveData): androidx.room3:room3-livedata আর্টিফ্যাক্ট থেকে LiveDataDaoReturnTypeConverter রেজিস্টার করুন।

InvalidationTracker Observer সরানো হয়েছে কিনা তা যাচাই করুন

Room 3.0 সম্পূর্ণভাবে InvalidationTracker.Observer এবং সম্পর্কিত রেজিস্ট্রেশন পদ্ধতি সরিয়ে দেয়, যেমন addObserver এবং removeObserver।

আপনি যদি আগে থেকে ফেজ ১-এ কোরুটিন ফ্লোতে ট্রানজিশন না করে থাকেন, তাহলে আপনাকে অবশ্যই সব Observer ব্যবহার createFlow-এ মাইগ্রেট করতে হবে:

val userFlow = db.invalidationTracker.createFlow("User").map { _ ->
    userDao.getAllUsers()
}