גרסה 3.0 של Room היא עדכון גרסה משמעותי שמעביר את הספרייה למצב Kotlin-first. הוא תומך ב-Kotlin Multiplatform (KMP), דורש Kotlin Symbol Processing (KSP) ומחייב שימוש ב-coroutines לפעולות אסינכרוניות.
כדי למנוע בעיות תאימות עם אפליקציות קיימות של 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. Room 3.0 עדיין תומך במקורות Java כקלט.
- קודים קורוטיניים קודמים: פונקציות DAO חייבות להיות פונקציות
suspend, למעט סוגים של נתונים שניתנים לצפייה. CoroutineContextמחליף את המשתמשים שמריצים את התהליך. - אין SupportSQLite: ממשקי
SQLiteDriverAPI מגבים את 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.
עדכון לגרסה Room 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 compiler: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 בבלוקים שלwithWriteTransactionאוwithReadTransaction:
// Before (withTransaction)
db.withTransaction {
// perform database operations
}
// After (withWriteTransaction - Room 2.8)
import androidx.room.withWriteTransaction
db.withWriteTransaction {
// perform database operations
}
אם אתם צריכים גישה ישירה ברמה נמוכה לחיבור העסקה, אתם יכולים להשתמש גם ב-useWriterConnection עם immediateTransaction.
- מומלץ להימנע משימוש ישיר ב-
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ב-RoomDatabasebuilder:
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. הפעולה הזו מכינה את ה-codebase ל-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()
}