To prevent queries from blocking the UI, Room doesn't support database access on the main thread. This restriction means you must make your DAO queries asynchronous. The Room library includes integrations with several frameworks to provide asynchronous query execution.
DAO queries fall into three categories:
- One-shot write queries that insert, update, or delete data in the database.
- One-shot read queries that read data from your database only once and return a result with the snapshot of the database at that time.
- Observable read queries that read data from your database every time the underlying database tables change and emit new values to reflect those changes.
Language and framework options
Room provides integration support for interoperability with specific language features and libraries. The following table shows applicable return types based on query type and framework:
| Query type | Kotlin language features (Native) | RxJava | Guava | Jetpack Lifecycle* |
|---|---|---|---|---|
| One-shot write | Coroutines (suspend) |
Single<T>, Maybe<T>,
Completable |
ListenableFuture<T> |
N/A |
| One-shot read | Coroutines (suspend) |
Single<T>, Maybe<T> |
ListenableFuture<T> |
N/A |
| Observable read | Flow<T> |
Flowable<T>, Publisher<T>,
Observable<T> |
N/A | LiveData<T> |
This guide demonstrates three ways to use these integrations to implement asynchronous queries in your DAOs.
Kotlin with Flow and coroutines
Kotlin provides built-in language features that let you write asynchronous queries without third-party frameworks:
- Room directly supports Kotlin's Flow to write observable queries.
- Room requires the
suspendkeyword to make your one-shot DAO queries asynchronous with Kotlin coroutines.
Coroutines and Flow support is built directly into the core Room runtime, so no additional artifacts are required.
RxJava for Kotlin and Java
Room 3.0 supports RxJava 3 return types. To use RxJava return types, you must register the RxJava return type converters in your database or DAO:
- Include the
androidx.room3:room3-rxjava3artifact in your build configuration. - Annotate your
@Databaseor@Daodeclaration with@DaoReturnTypeConverters(RxDaoReturnTypeConverters::class).
Room supports the following RxJava 3 return types:
- One-shot queries:
Completable,Single<T>, andMaybe<T> - Observable queries:
Publisher<T>,Flowable<T>, andObservable<T>
LiveData and Guava
Room 3.0 supports LiveData and Guava ListenableFuture return types using
converters:
- LiveData: Include the
androidx.room3:room3-livedataartifact and annotate your database or DAO with@DaoReturnTypeConverters(LiveDataDaoReturnTypeConverter::class). - Guava: Include the
androidx.room3:room3-guavaartifact and annotate your database or DAO with@DaoReturnTypeConverters(GuavaDaoReturnTypeConverter::class).
Write asynchronous one-shot queries
One-shot queries are database operations that only run once and grab a snapshot of data at the time of execution. Here are some examples of asynchronous one-shot queries:
@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> }
Write observable queries
Observable queries are read operations that emit new values whenever the referenced tables change. For example, you can use this behavior to keep a displayed list of items updated as the database changes. Here are some examples of observable queries:
@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>> }
Track database invalidation manually
When you need to build observable database operations manually, you can use the
createFlow API of InvalidationTracker. This API lets you
create a Flow that tracks modifications to specific tables and emits a
notification whenever those tables change.
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) } }
By default, the returned Flow emits an initial value containing all the
registered tables to kick-start the stream. You can disable this behavior by
setting the emitInitialState parameter to false.
Custom DAO return type converters
For types that aren't directly supported by Room or its extension libraries,
you can define custom DAO return type converters to support additional return
types. To transform the result of a DAO function into your custom type,
annotate a converter function with @DaoReturnTypeConverter.
For example, you can define a converter that uses androidx.tracing to add
trace sections around the execution of a query to monitor performance-sensitive
queries by wrapping the execution in a custom TracedQuery type:
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) } }
To use the converter, annotate your database or DAO with
@DaoReturnTypeConverters:
@Dao @DaoReturnTypeConverters(TracingDaoReturnTypeConverter::class) interface MusicDao { @Query("SELECT * FROM Song") suspend fun getAllSongs(): TracedQuery<List<Song>> }
Control DAO return type converter initialization
Ordinarily, Room handles instantiation of DAO return type converters.
However, if you must pass additional dependencies to your converter classes,
your app must directly control their initialization. If so, annotate your
converter class with @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) } }
Then, in addition to declaring your converter class in
@DaoReturnTypeConverters, use the
RoomDatabase.Builder.addDaoReturnTypeConverter function to pass an
instance of your converter class to the RoomDatabase builder:
val db = Room.databaseBuilder<MyDatabase>(applicationContext, "database-name") .addDaoReturnTypeConverter(TracingDaoReturnTypeConverter(myLoggerInstance)) .build()
Converter function requirements
A @DaoReturnTypeConverter function must meet several requirements:
- It must have a functional parameter as its last argument, usually named
executeAndConvert. This parameter is asuspendlambda that Room generates to execute the query and parse the result.- If the converter needs to transform the query, such as Paging, the
lambda can take a
RoomRawQueryparameter.
- If the converter needs to transform the query, such as Paging, the
lambda can take a
- It can optionally accept the following parameters before the lambda:
db: RoomDatabase: Accesses the database instance, which is useful for obtaining the coroutine scope or performing additional operations.tableNames: Array<String>orList<String>: Provides the names of the tables accessed by the query, which is useful for observable types.rawQuery: RoomRawQuery: Provides the runtime instance of the query.inTransaction: Boolean: Indicates whether the query is executing within a transaction.