Von Room 2.x zu Room 3.0 migrieren

Room 3.0 ist ein wichtiges Versionsupdate, mit dem die Bibliothek auf Kotlin umgestellt wird. Es unterstützt Kotlin Multiplatform (KMP), erfordert Kotlin Symbol Processing (KSP) und erzwingt Koroutinen für asynchrone Vorgänge.

Um Kompatibilitätsprobleme mit vorhandenen Room 2.x-Apps und transitiven Abhängigkeiten zu vermeiden, befindet sich Room 3.0 in einem neuen Paket: androidx.room3.

In dieser Anleitung werden die Schritte beschrieben, die zum Migrieren Ihrer vorhandenen Room 2.x-Implementierung zu Room 3.0 erforderlich sind.

Wichtige Änderungen in Room 3.0

Machen Sie sich vor Beginn der Migration mit den wichtigsten Unterschieden vertraut:

  • Neues Paket und neue Artefakte: Alle Klassen befinden sich in androidx.room3. Artefakte verwenden das room3 Präfix, z. B. androidx.room3:room3-runtime.
  • Nur Kotlin und KSP: Room 3.0 unterstützt keine Java-Code-Generierung. Verwenden Sie KSP anstelle von KAPT oder Java-Annotation-Processors. Room 3.0 unterstützt weiterhin Java-Quellen als Eingaben.
  • Koroutinen zuerst: DAO-Funktionen müssen suspend Funktionen sein, mit Ausnahme von beobachtbaren Typen. CoroutineContext ersetzt Ausführer.
  • Kein SupportSQLite: SQLiteDriver APIs unterstützen Room. Room entfernt SupportSQLiteDatabase aus den Kern-APIs.
  • API-Änderungen: Migrationen und Datenbank-Callbacks verwenden SQLiteConnection anstelle von SupportSQLiteDatabase.
  • Konverter für reaktive Typen: Für Rückgabetypen von RxJava, LiveData, Guava und Paging müssen Sie @DaoReturnTypeConverters registrieren.

Wir empfehlen, die Migration in zwei Phasen durchzuführen: Zuerst bereiten Sie Ihre Codebasis in Room 2.x vor und modernisieren sie, dann wechseln Sie zu Room 3.0.


Vorbereitung und Modernisierung in Room 2.x

Bevor Sie zu Room 3.0 migrieren, können Sie die meisten Modernisierungsarbeiten durchführen, indem Sie auf die aktuelle Room 2.x-Version aktualisieren, z. B. Room 2.8. Room 2.8 unterstützt Kotlin Multiplatform (KMP) und enthält viele Treiber-APIs, die von Room 3.0 verwendet werden.

Auf Room 2.8 und höher aktualisieren

Aktualisieren Sie Ihre Build-Konfiguration, um die aktuelle Room 2.x-Version zu verwenden:

[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" }

Von KAPT zu KSP migrieren

Room 3.0 unterstützt keine Java-Annotation-Processors oder KAPT. Sie müssen Kotlin Symbol Processing (KSP) verwenden. Sie können diese Umstellung vornehmen, während Sie noch Room 2.x verwenden.

  1. Wenden Sie in der Datei build.gradle.kts Ihres Moduls das KSP-Plug-in an:

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

    Achten Sie darauf, dass die KSP-Version mit Ihrer Kotlin-Version kompatibel ist.

  2. Ersetzen Sie kapt oder annotationProcessor durch ksp für die Abhängigkeit des Room-Compilers:

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

Koroutinen verwenden

Room 3.0 erfordert Koroutinen für asynchrone Vorgänge.

  • Aktualisieren Sie Ihre DAOs: Sofern sie keinen beobachtbaren reaktiven Typ wie Flow oder RxJava-Typen zurückgeben, müssen alle DAO-Funktionen suspend-Funktionen sein.
// 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>
}
  • Wenn Sie Ihre RoomDatabase mit einem benutzerdefinierten Executor für Datenbankvorgänge konfiguriert haben, migrieren Sie mit setQueryCoroutineContext im Builder zu CoroutineContext:
Room.databaseBuilder<AppDatabase>(context, "db")
    .setQueryCoroutineContext(Dispatchers.IO)
    .build()

Treiber-APIs verwenden und Support SQLite vermeiden

Room 3.0 wird vollständig von SQLiteDriver unterstützt und unterstützt SupportSQLiteDatabase nicht mehr in den Kern-APIs.

Wenn Sie setDriver nicht aufrufen, um einen SQLiteDriver für Ihren Datenbank-Builder festzulegen, wird Room 2.8 im Kompatibilitätsmodus ausgeführt, in dem sowohl Support SQLite als auch Treiber-APIs funktionieren. In diesem Kompatibilitätsmodus können Sie Ihre Codebasis schrittweise konvertieren, bevor Sie den Treiber aktivieren.

  • Migrationen konvertieren: Migrieren Sie Ihre Migration und AutoMigrationSpec Unterklassen, um SQLiteConnection anstelle von SupportSQLiteDatabase zu verwenden.
// 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"
        )
    }
}
  • Datenbank-Callbacks konvertieren: Aktualisieren Sie die RoomDatabase.Callback Implementierungen, um SQLiteConnection zu verwenden:
// 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-Funktionen konvertieren: Verwenden Sie für Funktionen, die mit @RawQuery annotiert sind, RoomRawQuery anstelle von SupportSQLiteQuery:
// Before (SupportSQLiteQuery)
@Dao
interface UserDao {
    @RawQuery
    fun getUser(query: SupportSQLiteQuery): User
}

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

Sie können zur Laufzeit eine RoomRawQuery erstellen:

val query = RoomRawQuery(
    sql = "SELECT * FROM User WHERE id = ?",
    onBindStatement = { statement ->
        statement.bindInt(1, userId)
    }
)
  • Transaktions-APIs konvertieren: Ersetzen Sie die Android-spezifischen Blöcke withTransaction und runInTransaction durch withWriteTransaction oder withReadTransaction:
// Before (withTransaction)
db.withTransaction {
    // perform database operations
}

// After (withWriteTransaction - Room 2.8)
import androidx.room.withWriteTransaction

db.withWriteTransaction {
    // perform database operations
}

Wenn Sie direkten Zugriff auf die Transaktionsverbindung auf niedriger Ebene benötigen, können Sie auch useWriterConnection mit immediateTransaction verwenden.

  • Direkte Verwendung von SupportSQLiteDatabase vermeiden: Wenn Sie umfangreichen Legacy-Code haben, der weiterhin SupportSQLiteDatabase erfordert und den Sie noch nicht migrieren können, verwenden Sie das Komuserpatibilitätsartefakt androidx.room:room-sqlite-wrapper:
dependencies {
    implementation("androidx.room:room-sqlite-wrapper:$roomVersion")
}

Verwenden Sie dann getSupportWrapper, um eine SupportSQLiteDatabase aus Ihrer Room-Datenbankinstanz abzurufen:

import androidx.room.support.getSupportWrapper

val legacyDb: SupportSQLiteDatabase = roomDatabase.getSupportWrapper()
  • SQLite-Treiber festlegen: Nachdem Sie alle Room-API-Verwendungen zu Treiber APIs migriert haben, konfigurieren Sie einen Treiber wie BundledSQLiteDriver oder AndroidSQLiteDriver, indem Sie setDriver in Ihrem RoomDatabase Builder aufrufen:
import androidx.sqlite.driver.bundled.BundledSQLiteDriver

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

Flow-basiertes Tracking von Außerkraftsetzungen verwenden

In Room 2.8 wird die API InvalidationTracker.createFlow eingeführt. Verwenden Sie diese API, um von älteren InvalidationTracker.Observer-Implementierungen zu migrieren, während Sie noch Room 2.x verwenden. So bereiten Sie Ihre Codebasis auf Room 3.0 vor, in dem Observer vollständig entfernt wird.

// 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()
}

Zu Room 3.0 migrieren

Nachdem Sie Ihre Anwendung in Room 2.x modernisiert haben, müssen Sie für die Umstellung auf Room 3.0 Abhängigkeiten, Paketimporte und Datenbank-Callbacks aktualisieren.

Abhängigkeiten und Paketimporte aktualisieren

  • Ersetzen Sie in Ihrer Build-Konfiguration androidx.room-Abhängigkeiten durch 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" }
  • Aktualisieren Sie den Block mit den Abhängigkeiten:
dependencies {
    implementation(libs.androidx.room3.runtime)
    ksp(libs.androidx.room3.compiler)
}
  • Aktualisieren Sie Ihre Paketimporte. Ersetzen Sie import androidx.room.* durch import androidx.room3.*.

APIs für Typkonverter aktualisieren

In Room 3.0 werden die APIs für Typkonverter umbenannt, um ihre Verwendung für die Konvertierung von Spaltenwerten zu verdeutlichen und Verwechslungen mit den DAO-Rückgabetypkonvertern zu vermeiden.

Aktualisieren Sie die folgenden Annotationen und Funktionen in Ihrer Codebasis:

  • Benennen Sie @TypeConverter in @ColumnTypeConverter um.
  • Benennen Sie @TypeConverters in @ColumnTypeConverters um.
  • Benennen Sie @ProvidedTypeConverter in @ProvidedColumnTypeConverter um.
  • Benennen Sie RoomDatabase.Builder.addTypeConverter in addColumnTypeConverter um.

Beispiel:

// 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()

Callbacks in suspend-Funktionen aktualisieren

In Room 3.0 verwenden Datenbank-Callbacks und Migrationen SQLiteConnection und sind suspend-Funktionen.

  • Aktualisieren Sie Ihre manuellen Migration-Klassen:
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"
        )
    }
}
  • Aktualisieren Sie Ihre RoomDatabase.Callback-Implementierungen:
val callback = object : RoomDatabase.Callback() {
    override suspend fun onCreate(connection: SQLiteConnection) {
        // ...
    }
}

DAO-Rückgabetypkonverter registrieren

In Room 3.0 müssen Sie für reaktive Rückgabetypen wie RxJava, LiveData, Guava und Paging DAO-Rückgabetypkonverter mit @DaoReturnTypeConverters registrieren.

import androidx.room3.paging.PagingSourceDaoReturnTypeConverter

@Dao
@DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class)
interface UserDao {
    @Query("SELECT * FROM User")
    fun getAllPaginated(): PagingSource<Int, User>
}
  • Paging (PagingSource): Registrieren Sie PagingSourceDaoReturnTypeConverter aus dem androidx.room3:room3-paging Artefakt.
  • RxJava (Observable, Flowable, Single, Maybe, Completable) : Registrieren Sie RxDaoReturnTypeConverters aus dem Artefakt androidx.room3:room3-rxjava3.
  • Guava (ListenableFuture): Registrieren Sie GuavaDaoReturnTypeConverter aus dem Artefakt androidx.room3:room3-guava.
  • LiveData (LiveData): Registrieren Sie LiveDataDaoReturnTypeConverter aus dem androidx.room3:room3-livedata Artefakt.

Entfernung von InvalidationTracker-Observern bestätigen

In Room 3.0 werden InvalidationTracker.Observer und zugehörige Registrierungsmethoden wie addObserver und removeObserver vollständig entfernt.

Wenn Sie in Phase 1 noch nicht zu Koroutinen-Flows migriert haben, müssen Sie alle Observer Verwendungen zu createFlow migrieren:

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