আপনার অ্যাপ যদি উল্লেখযোগ্য পরিমাণে স্ট্রাকচার্ড ডেটা নিয়ে কাজ করে, তবে সেই ডেটা স্থানীয়ভাবে সংরক্ষণ করলে আপনি ব্যাপকভাবে উপকৃত হতে পারেন। এর সবচেয়ে সাধারণ ব্যবহার হলো প্রাসঙ্গিক ডেটা ক্যাশ করে রাখা, যাতে ডিভাইসটি নেটওয়ার্ক অ্যাক্সেস করতে না পারলেও আপনি অফলাইনে থাকা অবস্থায় সেই কন্টেন্ট ব্রাউজ করতে পারেন।
Room পার্সিস্টেন্স লাইব্রেরিটি SQLite-এর উপর একটি অ্যাবস্ট্রাকশন লেয়ার প্রদান করে, যা আপনাকে SQLite-এর সম্পূর্ণ শক্তিকে কাজে লাগানোর পাশাপাশি সাবলীলভাবে ডাটাবেস অ্যাক্সেস করতে দেয়।
রুম ২.x প্রস্তুত করুন
আপনার অ্যাপে Room 2.x ব্যবহার করতে, আপনার অ্যাপের build.gradle ফাইলে নিম্নলিখিত ডিপেন্ডেন্সিগুলো যোগ করুন:
dependencies {
val room_version = "2.6.1"
implementation("androidx.room:room-runtime:$room_version")
annotationProcessor("androidx.room:room-compiler:$room_version")
// To use Kotlin Symbol Processing (KSP)
// ksp("androidx.room:room-compiler:$room_version")
// optional - Kotlin Extensions and Coroutines support for Room
implementation("androidx.room:room-ktx:$room_version")
// optional - RxJava2 support for Room
implementation("androidx.room:room-rxjava2:$room_version")
// optional - Guava support for Room, including Optional and ListenableFuture
implementation("androidx.room:room-guava:$room_version")
// optional - Test helpers
testImplementation("androidx.room:room-testing:$room_version")
}
প্রাথমিক উপাদান
কক্ষের তিনটি প্রধান উপাদান রয়েছে:
- ডাটাবেস ক্লাস যা ডাটাবেস ধারণ করে এবং আপনার অ্যাপের সংরক্ষিত ডেটাতে অন্তর্নিহিত সংযোগের জন্য প্রধান অ্যাক্সেস পয়েন্ট হিসেবে কাজ করে।
- ডেটা এনটিটি যা আপনার অ্যাপের ডেটাবেসের টেবিলগুলোকে উপস্থাপন করে।
- ডেটা অ্যাক্সেস অবজেক্ট (DAO) হলো এমন কিছু মেথড যা ব্যবহার করে আপনার অ্যাপ ডাটাবেস থেকে ডেটা কোয়েরি, আপডেট, ইনসার্ট এবং ডিলিট করতে পারে।
চিত্র ১-এ কক্ষের বিভিন্ন উপাদানগুলোর মধ্যকার সম্পর্ক দেখানো হয়েছে।

নমুনা বাস্তবায়ন
// Entity
@Entity
data class User(
@PrimaryKey val uid: Int,
@ColumnInfo(name = "first_name") val firstName: String?,
@ColumnInfo(name = "last_name") val lastName: String?
)
// DAO
@Dao
interface UserDao {
@Query("SELECT * FROM user")
fun getAll(): List<User>
@Insert
fun insertAll(vararg users: User)
@Delete
fun delete(user: User)
}
// Database
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
// Usage
val db = Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "database-name"
).build()
val userDao = db.userDao()
val users: List<User> = userDao.getAll()
এনটিটি ব্যবহার করে ডেটা সংজ্ঞায়িত করুন
প্রতিটি Room এনটিটি ডাটাবেসের একটি টেবিলকে প্রতিনিধিত্ব করে। আপনি প্রতিটি এনটিটিকে @Entity অ্যানোটেশনযুক্ত একটি ক্লাস হিসেবে সংজ্ঞায়িত করেন।
@Entity(tableName = "users")
data class User (
@PrimaryKey val id: Int,
@ColumnInfo(name = "first_name") val firstName: String?,
@ColumnInfo(name = "last_name") val lastName: String?,
@Ignore val picture: Bitmap? = null
)
- কাস্টম টেবিল এবং কলামের নাম : ডিফল্টরূপে, Room ক্লাসের নামকে টেবিলের নাম এবং প্রপার্টির নামকে কলামের নাম হিসেবে ব্যবহার করে। এগুলো কাস্টমাইজ করতে,
@EntityতেtableNameপ্রপার্টি এবং@ColumnInfo(name = "...")অ্যানোটেশন ব্যবহার করুন। - প্রাইমারি কী : একটি প্রাইমারি কী নির্ধারণ করতে,
@PrimaryKeyব্যবহার করুন। কম্পোজিট কী-এর জন্য,@EntityএরprimaryKeysপ্রপার্টি ব্যবহার করুন:@Entity(primaryKeys = ["firstName", "lastName"])। - ফিল্ড উপেক্ষা করুন : ফিল্ড সংরক্ষিত হওয়া থেকে বিরত রাখতে,
@Ignoreব্যবহার করুন।
টাইপ কনভার্টার
কখনও কখনও, আপনার একটিমাত্র কলামে Date মতো কাস্টম টাইপ সংরক্ষণ করার প্রয়োজন হতে পারে। কাস্টম টাইপগুলোকে Room-এর পার্সিস্টযোগ্য টাইপগুলোতে রূপান্তর করতে এবং সেগুলোর থেকে রূপান্তর করতে @TypeConverter মেথড প্রদান করুন।
class Converters {
@TypeConverter
fun fromTimestamp(value: Long?): Date? = value?.let { Date(it) }
@TypeConverter
fun dateToTimestamp(date: Date?): Long? = date?.time
}
// Register in your Database class
@Database(entities = [User::class], version = 1)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() { ... }
DAO ব্যবহার করে ডেটা অ্যাক্সেস করুন
DAO-গুলি ডাটাবেসের সাথে ইন্টারঅ্যাকশনের পদ্ধতি নির্ধারণ করে। ইন্টারফেস বা অ্যাবস্ট্রাক্ট ক্লাসকে @Dao দিয়ে অ্যানোটেট করুন।
সুবিধাজনক পদ্ধতি
@Dao
interface UserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertUsers(vararg users: User)
@Update
fun updateUsers(vararg users: User)
@Delete
fun deleteUsers(vararg users: User)
}
- Insert : insert মেথডটি সন্নিবেশিত সারির আইডি নির্দেশকারী একটি
Long, অথবা সন্নিবেশিত সমস্ত সারির আইডি ধারণকারী একটিList<Long>রিটার্ন করতে পারে। - আপডেট বা ডিলিট : আপডেট বা ডিলিট মেথডটি প্রভাবিত সারির সংখ্যা নির্দেশকারী একটি
Intরিটার্ন করতে পারে।
কোয়েরি পদ্ধতি
SQL স্টেটমেন্ট লেখার জন্য মেথডগুলোকে @Query দিয়ে টীকাযুক্ত করুন। Room কম্পাইল করার সময় কোয়েরিগুলো যাচাই করে।
@Dao
interface UserDao {
// Simple query
@Query("SELECT * FROM user")
fun loadAllUsers(): Array<User>
// Return a subset of columns using a POJO or tuple
@Query("SELECT first_name, last_name FROM user")
fun loadFullName(): List<NameTuple>
// Pass parameters
@Query("SELECT * FROM user WHERE age > :minAge")
fun loadAllUsersOlderThan(minAge: Int): Array<User>
// Collection of parameters
@Query("SELECT * FROM user WHERE region IN (:regions)")
fun loadUsersFromRegions(regions: List<String>): List<User>
// Join tables
@Query("SELECT * FROM book INNER JOIN user ON user.id = book.user_id WHERE user.name = :userName")
fun findBooksBorrowedByName(userName: String): List<Book>
}
মাল্টিম্যাপ রিটার্ন টাইপ
রুম ২.৪ এবং তার পরবর্তী সংস্করণগুলোতে, কোয়েরি মেথডগুলো Map টাইপ ব্যবহার করে সরাসরি একটি মাল্টিম্যাপ রিটার্ন করতে পারে:
@Query("SELECT * FROM user JOIN book ON user.id = book.user_id")
fun loadUserAndBookNames(): Map<User, List<Book>>
অ্যাসিঙ্ক্রোনাস ডিএও কোয়েরি
UI ফ্রিজ হওয়া এড়াতে, ডাটাবেস কোয়েরি মেইন থ্রেডে চালানো যায় না। নিম্নলিখিত ইন্টিগ্রেশনগুলির মধ্যে একটি ব্যবহার করে আপনার কোয়েরিগুলিকে অ্যাসিঙ্ক্রোনাস করুন:
কোটলিন কোরাউটিন এবং ফ্লো
room-ktx নির্ভরতা প্রয়োজন।
@Dao
interface UserDao {
// One-shot async query
@Insert
suspend fun insertUsers(vararg users: User)
// Observable query using Flow
@Query("SELECT * FROM user WHERE id = :id")
fun loadUserById(id: Int): Flow<User>
}
জাভা এবং RxJava
room-rxjava2 অথবা room-rxjava3 প্রয়োজন।
@Dao
interface UserDao {
@Insert
fun insertUsers(users: List<User>): Completable
@Query("SELECT * FROM user WHERE id = :id")
fun loadUserById(id: Int): Flowable<User>
}
জাভা, লাইভডেটা এবং গুয়াভা
ListenableFuture এর জন্য room-guava প্রয়োজন।
@Dao
interface UserDao {
// LiveData for observable queries
@Query("SELECT * FROM user WHERE id = :id")
fun loadUserById(id: Int): LiveData<User>
// Guava ListenableFuture for one-shot queries
@Insert
fun insertUsers(users: List<User>): ListenableFuture<Integer>
}
রুম 2.x-এ সম্পর্কগুলো সংজ্ঞায়িত করুন।
UI থ্রেডে লেজি লোডিং প্রতিরোধ করতে, আপনি এনটিটিগুলোর মধ্যে সরাসরি অবজেক্ট রেফারেন্স ব্যবহার করতে পারবেন না। এর পরিবর্তে, @Relation সহ মধ্যবর্তী ডেটা ক্লাস ব্যবহার করে সম্পর্ক সংজ্ঞায়িত করুন।
একের সাথে এক
প্রতিটি ব্যবহারকারীর কেবল একটি লাইব্রেরি থাকে।
@Entity
data class User(@PrimaryKey val userId: Long, val name: String)
@Entity
data class Library(@PrimaryKey val libraryId: Long, val userOwnerId: Long)
// Intermediate class
data class UserAndLibrary(
@Embedded val user: User,
@Relation(
parentColumn = "userId",
entityColumn = "userOwnerId"
)
val library: Library
)
// DAO Query
@Transaction
@Query("SELECT * FROM User")
fun getUsersAndLibraries(): List<UserAndLibrary>
এক থেকে অনেকের
প্রতিটি ব্যবহারকারীর একাধিক প্লেলিস্ট থাকতে পারে।
@Entity
data class Playlist(@PrimaryKey val playlistId: Long, val userCreatorId: Long)
data class UserWithPlaylists(
@Embedded val user: User,
@Relation(
parentColumn = "userId",
entityColumn = "userCreatorId"
)
val playlists: List<Playlist>
)
বহু-থেকে-বহু
প্লেলিস্টে অনেক গান থাকতে পারে এবং একটি গান একাধিক প্লেলিস্টে থাকতে পারে। এর জন্য একটি জাংশন টেবিল প্রয়োজন।
@Entity
data class Song(@PrimaryKey val songId: Long, val songName: String)
@Entity(primaryKeys = ["playlistId", "songId"])
data class PlaylistSongCrossRef(val playlistId: Long, val songId: Long)
data class PlaylistWithSongs(
@Embedded val playlist: Playlist,
@Relation(
parentColumn = "playlistId",
entityColumn = "songId",
associateBy = Junction(PlaylistSongCrossRef::class)
)
val songs: List<Song>
)
আন্তঃসম্পর্ক
ব্যবহারকারী, তাদের প্লেলিস্ট এবং সেই প্লেলিস্টগুলিতে থাকা সমস্ত গান অনুসন্ধান করুন।
data class UserWithPlaylistsAndSongs(
@Embedded val user: User,
@Relation(
entity = Playlist::class,
parentColumn = "userId",
entityColumn = "userCreatorId"
)
val playlists: List<PlaylistWithSongs> // Nesting PlaylistWithSongs
)
ডাটাবেস ব্যবস্থাপনা
এই বিভাগে আপনার রুম ডেটাবেস পরিচালনার বিভিন্ন দিক আলোচনা করা হয়েছে, যার মধ্যে রয়েছে ডেটাবেস ভিউ, ডেটা আগে থেকে পূরণ করা এবং ডেটাবেস মাইগ্রেশন।
ডাটাবেস ভিউ
@DatabaseView অ্যানোটেশনযুক্ত একটি ক্লাসের মধ্যে একটি জটিল কোয়েরিকে আবদ্ধ করুন।
@DatabaseView("SELECT user.id, user.name, department.name AS departmentName FROM user INNER JOIN department ON user.departmentId = department.id")
data class UserDetail(val id: Long, val name: String, val departmentName: String)
// Register in Database class
@Database(entities = [User::class], views = [UserDetail::class], version = 1)
abstract class AppDatabase : RoomDatabase() { ... }
ডাটাবেস আগে থেকে পূরণ করুন
প্রারম্ভিক পর্যায়ে একটি অ্যাসেট ফাইল বা ফাইল সিস্টেম থেকে ডাটাবেসটি পূরণ করুন।
Room.databaseBuilder(appContext, AppDatabase::class.java, "Sample.db")
.createFromAsset("database/myapp.db")
.build()
অভিবাসন
যখন আপনি স্কিমা পরিবর্তন করবেন, তখন ডাটাবেস ভার্সন বাড়িয়ে দিন এবং একটি Migration অবজেক্ট সংজ্ঞায়িত করুন।
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE User ADD COLUMN age INTEGER NOT NULL DEFAULT 0")
}
}
Room.databaseBuilder(applicationContext, AppDatabase::class.java, "database-name")
.addMigrations(MIGRATION_1_2)
.build()
- স্বয়ংক্রিয় মাইগ্রেশন : আপনি যদি Room 2.4.0 বা তার উচ্চতর সংস্করণ ব্যবহার করেন, তাহলে
@AutoMigrationব্যবহার করে মৌলিক স্কিমা পরিবর্তনগুলো স্বয়ংক্রিয়ভাবে মাইগ্রেট করতে পারেন। এর জন্য আপনার ডাটাবেস কনফিগারেশনেexportSchematrueসেট করতে হবে:@Database(version = 2, autoMigrations = [AutoMigration(from = 1, to = 2)]) - ধ্বংসাত্মক ফলব্যাক : মাইগ্রেশন পাথ অনুপস্থিত থাকলে ডেটা হারানো গ্রহণযোগ্য হলে, ডাটাবেস বিল্ড করার সময়
.fallbackToDestructiveMigrationকল করুন।
পরীক্ষার স্থানান্তর
মাইগ্রেশন যাচাই করতে, room-testing আর্টিফ্যাক্ট থেকে MigrationTestHelper ব্যবহার করুন। এটি সমর্থন করার জন্য, আপনার build.gradle কনফিগারেশনে স্কিমা এক্সপোর্ট করা নিশ্চিত করুন।
@RunWith(AndroidJUnit4::class)
class MigrationTest {
@get:Rule
val helper: MigrationTestHelper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
AppDatabase::class.java.canonicalName,
FrameworkSQLiteOpenHelperFactory()
)
@Test
fun migrate1To2() {
var db = helper.createDatabase("test-db", 1).apply {
execSQL("INSERT INTO User VALUES (1, 'John')")
close()
}
db = helper.runMigrationsAndValidate("test-db", 2, true, MIGRATION_1_2)
// Verify data was migrated correctly
}
}
SQLite থেকে Room-এ স্থানান্তরিত করুন
আপনার অ্যাপটি SQLite থেকে Room-এ স্থানান্তর করতে, নিম্নলিখিত ধাপগুলি সম্পন্ন করুন:
- Room অন্তর্ভুক্ত করতে নির্ভরতাগুলো আপডেট করুন ।
- মডেল ক্লাসগুলোকে
@Entity,@PrimaryKey, এবং@ColumnInfoদিয়ে টীকাযুক্ত করুন। - আপনার হেল্পার কোয়েরি মেথডগুলো প্রতিস্থাপন করতে ডিএও (DAO) তৈরি করুন ।
- আপনার এনটিটি এবং ডিএও-গুলিকে রেফারেন্স করে একটি RoomDatabase ক্লাস তৈরি করুন । ভার্সন নম্বরটি বাড়িয়ে দিন।
- একটি খালি মাইগ্রেশন পাথ সংজ্ঞায়িত করুন কারণ স্কিমা পরিবর্তন হয় না, শুধুমাত্র ফ্রেমওয়ার্ক পরিবর্তন হয়:
kotlin val MIGRATION_1_2 = object : Migration(1, 2) { override fun migrate(database: SupportSQLiteDatabase) {} } - মাইগ্রেশন পাথের সাথে
Room.databaseBuilderব্যবহার করার জন্য ইনস্ট্যানসিয়েশন আপডেট করুন ।