גרסה 3.0 של Room היא עדכון גרסה משמעותי שמעביר את הספרייה למצב Kotlin-first. היא תומכת ב-Kotlin Multiplatform (KMP), דורשת Kotlin Symbol Processing (KSP) ומחייבת שימוש בשגרות משנה לפעולות אסינכרוניות.
כדי למנוע בעיות תאימות עם אפליקציות קיימות של Room 2.x ותלות טרנזיטיבית, Room 3.0 נמצא בחבילה חדשה: androidx.room3.
במדריך הזה מפורטים השלבים הנדרשים להעברת ההטמעה הקיימת של Room 2.x ל-Room 3.0.
השינויים העיקריים בגרסה 3.0 של Room
לפני שמתחילים בהעברה, חשוב להכיר את ההבדלים העיקריים:
- חבילה וארטיפקטים חדשים: כל המחלקות נמצאות ב-
androidx.room3. חפצים משתמשים בקידומתroom3, כמוandroidx.room3:room3-runtime. - רק ב-Kotlin וב-KSP: גרסה 3.0 של Room לא תומכת ביצירת קוד Java. עדיף להשתמש ב-KSP במקום ב-KAPT או במעבדי הערות של Java. גרסה 3.0 של Room עדיין תומכת במקורות Java כקלט.
- קודים קורוטיניים קודמים: פונקציות DAO חייבות להיות פונקציות
suspend, למעט סוגים של נתונים שניתנים לצפייה. CoroutineContextמחליף את המשתמשים שמריצים את התהליך. - No SupportSQLite:
SQLiteDriverAPIs back Room. הסרת החדרSupportSQLiteDatabaseמממשקי API מרכזיים. - שינויים ב-API: העברות וקריאות חוזרות למסד נתונים משתמשות ב-
SQLiteConnectionבמקום ב-SupportSQLiteDatabase. - ממירים לסוגים ריאקטיביים: כדי להשתמש בסוגי ההחזרה 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.
עדכון לגרסה 2.8 ואילך של חדרים
כדי להשתמש בגרסה הנוכחית של Room 2.x, צריך לעדכן את הגדרות ה-build:
[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.
במודול
build.gradle.kts, מחילים את הפלאגין KSP:plugins { id("com.google.devtools.ksp") version "<ksp_version>" }מוודאים שגרסת ה-KSP תואמת לגרסת ה-Kotlin.
מחליפים את
kaptאוannotationProcessorב-kspעבור יחסי התלות של מהדר Room:dependencies { implementation(libs.androidx.room.runtime) ksp(libs.androidx.room.compiler) }
שימוש בשגרות משנה (coroutines)
ב-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>
}
- אם הגדרתם את
RoomDatabaseעםExecutorמותאם אישית כדי לבצע פעולות במסד נתונים, אתם יכולים לעבור אלCoroutineContextבאמצעותsetQueryCoroutineContextבכלי הבנייה:
Room.databaseBuilder<AppDatabase>(context, "db")
.setQueryCoroutineContext(Dispatchers.IO)
.build()
שימוש בממשקי API של מנהלי התקנים והימנעות מתמיכה ב-SQLite
Room 3.0 נתמך באופן מלא על ידי SQLiteDriver ולא תומך יותר ב-SupportSQLiteDatabase בממשקי ה-API הבסיסיים שלו.
אם לא קוראים ל-setDriver כדי להגדיר SQLiteDriver בבונה מסד הנתונים, Room 2.8 פועל במצב תאימות שבו פועלים גם ממשקי ה-API של Support SQLite וגם של Driver. מצב התאימות הזה מאפשר לכם להמיר את בסיס הקוד בהדרגה לפני הפעלת הדרייבר.
- המרת העברות: מעבירים את מחלקות המשנה
Migrationו-AutoMigrationSpecלשימוש ב-SQLiteConnectionבמקום ב-SupportSQLiteDatabase.
// 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"
)
}
}
- המרת קריאות חוזרות למסד נתונים: צריך לעדכן את ההטמעות של
RoomDatabase.Callbackכדי להשתמש ב-SQLiteConnection:
// 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) {
// ...
}
}
- המרת פונקציות DAO
@RawQuery: בפונקציות עם ההערה@RawQuery, משתמשים ב-RoomRawQueryבמקום ב-SupportSQLiteQuery:
// 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 של עסקאות: מחליפים את הבלוקים
withTransactionו-runInTransactionשפועלים רק ב-Android בבלוקים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")
}
לאחר מכן, משתמשים ב-getSupportWrapper כדי לקבל SupportSQLiteDatabase ממופע של מסד נתונים מסוג Room:
import androidx.room.support.getSupportWrapper
val legacyDb: SupportSQLiteDatabase = roomDatabase.getSupportWrapper()
- הגדרת דרייבר של SQLite: אחרי שמעבירים את כל השימושים ב-Room API ל-driver APIs, מגדירים דרייבר, כמו
BundledSQLiteDriverאוAndroidSQLiteDriver, על ידי קריאה ל-setDriverב-builder שלRoomDatabase:
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
val db = Room.databaseBuilder<AppDatabase>(context, "db")
.setDriver(BundledSQLiteDriver())
.build()
שימוש במעקב ביטולי תוקף מבוסס-זרימה
ב-Room 2.8 נוסף InvalidationTracker.createFlow API. אפשר להשתמש ב-API הזה כדי לעבור מהטמעות קודמות של InvalidationTracker.Observer בזמן שעדיין משתמשים בגרסה 2.x של Room. הפעולה הזו מכינה את בסיס הקוד ל-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()
}
מעבר לגרסה 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()
עדכון של פונקציות callback להשהיית פונקציות
ב-Room 3.0, קריאות חוזרות (callback) ומעברים של מסד נתונים משתמשים ב-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, מחייבים רישום של ממירים של סוגי החזרה של DAO באמצעות @DaoReturnTypeConverters.
import androidx.room3.paging.PagingSourceDaoReturnTypeConverter
@Dao
@DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class)
interface UserDao {
@Query("SELECT * FROM User")
fun getAllPaginated(): PagingSource<Int, User>
}
- החלפת דפים (
PagingSource): רישוםPagingSourceDaoReturnTypeConverterמהארטיפקטandroidx.room3:room3-paging. - RxJava (
Observable,Flowable,Single,Maybe,Completable): צריך לרשום אתRxDaoReturnTypeConvertersמהארטיפקטandroidx.room3:room3-rxjava3. - Guava (
ListenableFuture): רישום שלGuavaDaoReturnTypeConverterמתוך ארטיפקטandroidx.room3:room3-guava. - LiveData (
LiveData): הרשמהLiveDataDaoReturnTypeConverterמתוך הארטיפקטandroidx.room3:room3-livedata.
אימות ההסרה של InvalidationTracker Observer
בגרסה 3.0 של Room, InvalidationTracker.Observer והשיטות הקשורות לרישום, כמו addObserver ו-removeObserver, הוסרו לחלוטין.
אם עדיין לא עברתם לשימוש בזרימות של קורוטינות בשלב 1, אתם צריכים להעביר את כל השימושים ב-Observer ל-createFlow:
val userFlow = db.invalidationTracker.createFlow("User").map { _ ->
userDao.getAllUsers()
}