非同期 DAO クエリを作成する

クエリが UI をブロックしないように、Room はメインスレッドでのデータベース アクセスをサポートしていません。この制限は、DAO クエリを非同期にする必要があることを意味します。非同期クエリ実行を提供するために、Room ライブラリにはさまざまなフレームワークとの統合が含まれています。

DAO クエリは次の 3 つのカテゴリに分類されます。

  • データベースへのデータの挿入、更新、または削除を行うワンショット書き込みクエリ。
  • データベースからデータを 1 回だけ読み取り、その時点のデータベースのスナップショットで結果を返すワンショット読み取りクエリ。
  • 基になるデータベース テーブルが変更されるたびにデータベースからデータを読み取り、その変更を反映するために新しい値を出力するオブザーバブル読み取りクエリ。

言語とフレームワークのオプション

Room は、特定の言語機能やライブラリとの相互運用性のための統合サポートを提供します。次の表に、クエリタイプとフレームワークに基づいて、該当する戻り値の型を示します。

クエリタイプ Kotlin 言語機能(ネイティブ) RxJava Guava Jetpack Lifecycle*
ワンショット書き込み コルーチン(suspend Single<T>Maybe<T>Completable ListenableFuture<T> なし
ワンショット読み取り コルーチン(suspend Single<T>Maybe<T> ListenableFuture<T> なし
オブザーバブル読み取り Flow<T> Flowable<T>Publisher<T>Observable<T> なし LiveData<T>

このガイドでは、これらの統合を使用して DAO に非同期クエリを実装する方法を 3 つ紹介します。

Kotlin と Flow、コルーチン

Kotlin には、サードパーティのフレームワークを使用せずに非同期クエリを作成できる組み込みの言語機能があります。

  • Room は、オブザーバブル クエリを作成するために Kotlin の Flow を直接サポートしています。
  • Room では、Kotlin コルーチンを使用してワンショット DAO クエリを非同期にするために suspend キーワードが必要です。

コルーチンと Flow のサポートは Room のコア ランタイムに直接組み込まれているため、追加のアーティファクトは必要ありません。

Kotlin と Java 用の RxJava

Room 3.0 は RxJava 3 の戻り値の型をサポートしています。RxJava の戻り値の型を使用するには、データベースまたは DAO で RxJava の戻り値の型コンバータを登録する必要があります。

  1. ビルド構成に androidx.room3:room3-rxjava3 アーティファクトを含めます。
  2. @Database または @Dao の宣言に @DaoReturnTypeConverters(RxDaoReturnTypeConverters::class) アノテーションを付けます。

Room は、次の RxJava 3 の戻り値の型をサポートしています。

LiveData と Guava

Room 3.0 は、コンバータを使用して LiveData と Guava ListenableFuture の戻り値の型をサポートしています。

  • LiveData: androidx.room3:room3-livedata アーティファクトを含め、データベースまたは DAO に @DaoReturnTypeConverters(LiveDataDaoReturnTypeConverter::class) アノテーションを付けます。
  • Guava: androidx.room3:room3-guava アーティファクトを含め、データベースまたは DAO に @DaoReturnTypeConverters(GuavaDaoReturnTypeConverter::class) でアノテーションを付けます。

非同期ワンショット クエリを作成する

ワンショット クエリは、1 回だけ実行され、実行時にデータのスナップショットを取得するデータベース オペレーションです。非同期ワンショット クエリの例を次に示します。

@Dao
interface UserDao {
    @Query("SELECT * FROM user WHERE id = :id")
    suspend fun loadUserById(id: Int): User

    @Query("SELECT * from user WHERE region IN (:regions)")
    suspend fun loadUsersByRegion(regions: List<String>): List<User>
}

オブザーバブル クエリを作成する

オブザーバブル クエリは、参照されるテーブルが変更されるたびに新しい値を出力する読み取りオペレーションです。たとえば、この動作を使用して、データベースの変更に合わせて表示されているアイテムのリストを更新できます。オブザーバブル クエリの例を次に示します。

@Dao
interface ObservableUserDao {
    @Query("SELECT * FROM user WHERE id = :id")
    fun loadUserById(id: Int): Flow<User>

    @Query("SELECT * from user WHERE region IN (:regions)")
    fun loadUsersByRegion(regions: List<String>): Flow<List<User>>
}

データベースの無効化を手動で追跡する

観測可能なデータベース オペレーションを手動で構築する必要がある場合は、InvalidationTrackercreateFlow API を使用できます。この API を使用すると、特定のテーブルの変更を追跡し、それらのテーブルが変更されるたびに通知を送信する Flow を作成できます。

fun getArtistTours(db: RoomDatabase, from: Date, to: Date): Flow<Map<Artist, TourState>> {
    return db.invalidationTracker.createFlow("Artist").map { _ ->
        val artists = artistsDao.getAllArtists()
        val tours = tourService.fetchStates(artists.map { it.id })
        associateTours(artists, tours, from, to)
    }
}

デフォルトでは、返された Flow は、登録されたすべてのテーブルを含む初期値を放出して、ストリームを開始します。emitInitialState パラメータを false に設定することで、この動作を無効にできます。

カスタム DAO 戻り値の型コンバータ

Room またはその拡張ライブラリで直接サポートされていない型については、カスタム DAO 戻り値の型コンバータを定義して、追加の戻り値の型をサポートできます。DAO 関数の結果をカスタム型に変換するには、コンバータ関数に @DaoReturnTypeConverter アノテーションを付けます。

たとえば、androidx.tracing を使用して、クエリの実行前後にトレース セクションを追加するコンバータを定義し、実行をカスタム TracedQuery 型でラップして、パフォーマンスに影響するクエリをモニタリングできます。

class TracedQuery<T>(val result: T)

object TracingDaoReturnTypeConverter {
    @DaoReturnTypeConverter([OperationType.READ])
    suspend fun <T> convert(
        rawQuery: RoomRawQuery,
        executeAndConvert: suspend () -> T
    ): TracedQuery<T> {
        val result = trace("TracedQuery: ${rawQuery.sql}") {
            executeAndConvert()
        }
        return TracedQuery(result)
    }
}

コンバータを使用するには、データベースまたは DAO に @DaoReturnTypeConverters アノテーションを付けます。

@Dao
@DaoReturnTypeConverters(TracingDaoReturnTypeConverter::class)
interface MusicDao {
    @Query("SELECT * FROM Song")
    suspend fun getAllSongs(): TracedQuery<List<Song>>
}

DAO の戻り値の型コンバータの初期化を制御する

通常、Room は DAO 戻り値の型コンバータのインスタンス化を処理します。ただし、コンバータ クラスに追加の依存関係を渡す必要がある場合は、アプリで初期化を直接制御する必要があります。その場合は、コンバータ クラスに @ProvidedDaoReturnTypeConverter アノテーションを付けます。

@ProvidedDaoReturnTypeConverter
class TracingDaoReturnTypeConverter(val tracer: Tracer) {
    @DaoReturnTypeConverter([OperationType.READ])
    suspend fun <T> convert(
        rawQuery: RoomRawQuery,
        executeAndConvert: suspend () -> T
    ): TracedQuery<T> {
        val result = tracer.trace("TracedQuery: ${rawQuery.sql}") {
            executeAndConvert()
        }
        return TracedQuery(result)
    }
}

次に、@DaoReturnTypeConverters でコンバータ クラスを宣言するだけでなく、RoomDatabase.Builder.addDaoReturnTypeConverter 関数を使用してコンバータ クラスのインスタンスを RoomDatabase ビルダーに渡します。

val db = Room.databaseBuilder<MyDatabase>(applicationContext, "database-name")
    .addDaoReturnTypeConverter(TracingDaoReturnTypeConverter(myLoggerInstance))
    .build()

変換関数の要件

@DaoReturnTypeConverter 関数は、次の要件を満たす必要があります。

  • 最後の引数として関数パラメータが必要です。通常は executeAndConvert という名前が付けられます。このパラメータは、クエリを実行して結果を解析するために Room が生成する suspend ラムダです。
    • コンバータでクエリ(ページングなど)を変換する必要がある場合、ラムダは RoomRawQuery パラメータを受け取ることができます。
  • 必要に応じて、ラムダの前に次のパラメータを受け取ることができます。
    • db: RoomDatabase: データベース インスタンスにアクセスします。これは、コルーチン スコープを取得したり、追加のオペレーションを実行したりするのに役立ちます。
    • tableNames: Array<String> または List<String>: クエリによってアクセスされるテーブルの名前を提供します。これは、オブザーバブル タイプに役立ちます。
    • rawQuery: RoomRawQuery: クエリのランタイム インスタンスを提供します。
    • inTransaction: Boolean: クエリがトランザクション内で実行されているかどうかを示します。