Room 2.x から Room 3.0 に移行する

Room 3.0 は、ライブラリを Kotlin ファーストに移行するメジャー バージョン アップデートです。Kotlin Multiplatform(KMP)をサポートし、Kotlin Symbol Processing(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 ソースをサポートしています。
  • コルーチン ファースト: DAO 関数は、オブザーバブル型を除き、suspend 関数にする必要があります。CoroutineContext は実行プログラムに置き換わります。
  • SupportSQLite なし: SQLiteDriver API が Room をサポートします。Room はコア API から SupportSQLiteDatabase を削除します。
  • API の変更: 移行とデータベース コールバックでは、SupportSQLiteDatabase ではなく SQLiteConnection が使用されます。
  • リアクティブ型のコンバータ: RxJava、LiveData、Guava、Paging の戻り値の型では、@DaoReturnTypeConvertersを登録する必要があります。

2 つの異なるフェーズで移行することをおすすめします。まず、Room 2.x でコードベースを準備して最新化し、次に Room 3.0 に切り替えます。


Room 2.x で準備と最新化を行う

Room 3.0 に移行する前に、Room 2.8 などの現在の Room 2.x リリースに更新することで、ほとんどの最新化作業を行うことができます。Room 2.8 は Kotlin Multiplatform(KMP)をサポートし、Room 3.0 で使用される多くのドライバ API が含まれています。

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. Room コンパイラの依存関係で、kapt または annotationProcessorksp に置き換えます。

    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 を採用し、Support SQLite を回避する

Room 3.0 は SQLiteDriver によって完全にサポートされており、コア API で SupportSQLiteDatabase をサポートしなくなりました。

データベース ビルダーで SQLiteDriver を設定するために setDriver を呼び出さない場合、Room 2.8 は Support SQLite と Driver API の両方が機能する互換モードで動作します。この互換モードでは、ドライバを有効にする前にコードベースを段階的に変換できます。

  • 移行を変換する: MigrationAutoMigrationSpec サブクラスを移行して、SupportSQLiteDatabase ではなく SQLiteConnection を使用します。
// 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) {
        // ...
    }
}
  • @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 専用の withTransactionrunInTransaction ブロックを withWriteTransaction または withReadTransaction に置き換えます。
// Before (withTransaction)
db.withTransaction {
    // perform database operations
}

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

db.withWriteTransaction {
    // perform database operations
}

トランザクション接続に直接低レベルでアクセスする必要がある場合は、immediateTransactionuseWriterConnection を使用することもできます。

  • を直接使用しないSupportSQLiteDatabase: SupportSQLiteDatabase が必要な大規模な レガシー コードがあり、まだ移行できない場合は、androidx.room:room-sqlite-wrapper 互換性アーティファクトを使用します。
dependencies {
    implementation("androidx.room:room-sqlite-wrapper:$roomVersion")
}

次に、getSupportWrapper を使用して、Room データベース インスタンスから SupportSQLiteDatabase を取得します。

import androidx.room.support.getSupportWrapper

val legacyDb: SupportSQLiteDatabase = roomDatabase.getSupportWrapper()
  • SQLite ドライバを設定する: すべての Room API の使用をドライバ API に移行したら、RoomDatabase ビルダーで setDriver を呼び出して、BundledSQLiteDriverAndroidSQLiteDriver などのドライバを構成します。
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 実装から移行できます。これにより、Observer を完全に削除する Room 3.0 のコードベースを準備できます。

// 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.addTypeConverteraddColumnTypeConverter に名前変更します。

例:

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

コールバックを suspend 関数に更新する

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>
}
  • Paging (PagingSource): PagingSourceDaoReturnTypeConverterandroidx.room3:room3-paging アーティファクトから登録します。
  • RxJava(ObservableFlowableSingleMaybeCompletable: 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 と、addObserverremoveObserver などの関連する登録メソッドが完全に削除されます。

フェーズ 1 でコルーチン フローに移行していない場合は、すべての Observer の使用を createFlow に移行する必要があります。

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