Para evitar que las consultas bloqueen la IU, Room no admite el acceso a la base de datos en el subproceso principal. Esta restricción implica que tus búsquedas DAO deben ser asíncronas. La biblioteca de Room incluye integraciones con varios frameworks para permitir la ejecución de consultas asíncronas.
Las búsquedas DAO se dividen en tres categorías:
- Búsquedas de escritura única que insertan, actualizan o borran datos en la base de datos.
- Búsquedas de lectura única que leen datos de tu base de datos solo una vez y muestran un resultado con la instantánea de la base de datos en ese momento.
- Búsquedas de lectura observable que leen datos de tu base de datos cada vez que cambian las tablas de la base de datos subyacentes y emiten valores nuevos para reflejar esos cambios.
Opciones de framework y lenguaje
Room admite la interoperabilidad con funciones de lenguaje y bibliotecas específicas. En la siguiente tabla, se muestran los tipos de devolución aplicables según el tipo de búsqueda y el framework:
| Tipo de consulta | Funciones del lenguaje Kotlin (nativas) | RxJava | Guayaba | Lifecycle de Jetpack* |
|---|---|---|---|---|
| Escritura por única vez | Corrutinas (suspend) |
Single<T>, Maybe<T>,
Completable |
ListenableFuture<T> |
N/A |
| Lectura de una toma | Corrutinas (suspend) |
Single<T>, Maybe<T> |
ListenableFuture<T> |
N/A |
| Lectura observable | Flow<T> |
Flowable<T>, Publisher<T>,
Observable<T> |
N/A | LiveData<T> |
En esta guía, se muestran tres formas de usar estas integraciones para implementar búsquedas asíncronas en tus DAO.
Kotlin con Flow y corrutinas
Kotlin proporciona funciones de lenguaje integradas que te permiten escribir consultas asíncronas sin frameworks de terceros:
- Room admite directamente el Flow de Kotlin para escribir búsquedas observables.
- Room requiere la palabra clave
suspendpara que tus búsquedas DAO únicas sean asíncronas con corrutinas de Kotlin.
La compatibilidad con corrutinas y Flow está integrada directamente en el tiempo de ejecución principal de Room, por lo que no se requieren artefactos adicionales.
RxJava para Kotlin y Java
Room 3.0 admite tipos de datos que se muestran de RxJava 3. Para usar los tipos de datos que se devuelven de RxJava, debes registrar los convertidores de tipo de datos que se devuelven de RxJava en tu base de datos o DAO:
- Incluye el artefacto
androidx.room3:room3-rxjava3en tu configuración de compilación. - Anota tu
@Databaseo@Daodeclaración con@DaoReturnTypeConverters(RxDaoReturnTypeConverters::class).
Room admite los siguientes tipos de datos que se muestran de RxJava 3:
- Búsquedas únicas:
Completable,Single<T>, yMaybe<T> - Búsquedas observables:
Publisher<T>,Flowable<T>yObservable<T>
LiveData y Guava
Room 3.0 admite tipos de datos que se muestran de LiveData y Guava ListenableFuture con convertidores:
- LiveData: Incluye el artefacto
androidx.room3:room3-livedatay anota tu base de datos o DAO con@DaoReturnTypeConverters(LiveDataDaoReturnTypeConverter::class). - Guava: Incluye el artefacto
androidx.room3:room3-guavay anota tu base de datos o DAO con@DaoReturnTypeConverters(GuavaDaoReturnTypeConverter::class).
Escritura de búsquedas asíncronas únicas
Las búsquedas únicas son operaciones de base de datos que solo se ejecutan una vez y toman una instantánea de los datos en el momento de la ejecución. Estos son algunos ejemplos de búsquedas asíncronas únicas:
@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> }
Escritura de búsquedas observables
Las búsquedas observables son operaciones de lectura que emiten valores nuevos cada vez que cambian las tablas a las que se hace referencia. Por ejemplo, puedes usar este comportamiento para mantener actualizada una lista de elementos a medida que cambia la base de datos. Los siguientes son ejemplos comunes de búsquedas observables:
@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>> }
Realiza el seguimiento de la invalidación de la base de datos de forma manual
Cuando necesites compilar operaciones de base de datos observables de forma manual, puedes usar la
createFlow API de InvalidationTracker. Esta API te permite crear un Flow que realiza un seguimiento de las modificaciones en tablas específicas y emite una notificación cada vez que cambian esas tablas.
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) } }
De forma predeterminada, el Flow que se muestra emite un valor inicial que contiene todas las tablas registradas para iniciar la transmisión. Para inhabilitar este comportamiento, configura el parámetro emitInitialState como false.
Convertidores de tipo de datos que se devuelve de DAO personalizados
Para los tipos que Room o sus bibliotecas de extensión no admiten directamente, puedes definir convertidores de tipo de datos que se muestran de DAO personalizados para admitir tipos de datos que se muestran adicionales. Para transformar el resultado de una función DAO en tu tipo personalizado,
anota una función de convertidor con @DaoReturnTypeConverter.
Por ejemplo, puedes definir un convertidor que use androidx.tracing para agregar secciones de seguimiento en torno a la ejecución de una búsqueda para supervisar las búsquedas sensibles al rendimiento envolviendo la ejecución en un tipo TracedQuery personalizado:
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) } }
Para usar el convertidor, anota tu base de datos o DAO con
@DaoReturnTypeConverters:
@Dao @DaoReturnTypeConverters(TracingDaoReturnTypeConverter::class) interface MusicDao { @Query("SELECT * FROM Song") suspend fun getAllSongs(): TracedQuery<List<Song>> }
Cómo controlar la inicialización de convertidores de tipo de datos que se muestran de DAO
Por lo general, Room se ocupa de la creación de instancias de convertidores de tipo de datos que se muestran de DAO.
Sin embargo, si debes pasar dependencias adicionales a las clases de convertidores, tu app debe controlar directamente su inicialización. Si es así, anota tu
clase de convertidor con @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) } }
Luego, además de declarar tu clase de convertidor en
@DaoReturnTypeConverters, usa la
RoomDatabase.Builder.addDaoReturnTypeConverter función para pasar una
instancia de tu clase de convertidor al compilador RoomDatabase:
val db = Room.databaseBuilder<MyDatabase>(applicationContext, "database-name") .addDaoReturnTypeConverter(TracingDaoReturnTypeConverter(myLoggerInstance)) .build()
Requisitos de la función de convertidor
Una función @DaoReturnTypeConverter debe cumplir varios requisitos:
- Debe tener un parámetro funcional como su último argumento, que suele llamarse
executeAndConvert. Este parámetro es una expresión lambdasuspendque Room genera para ejecutar la búsqueda y analizar el resultado.- Si el convertidor necesita transformar la búsqueda, como Paging, la expresión lambda puede tomar un parámetro
RoomRawQuery.
- Si el convertidor necesita transformar la búsqueda, como Paging, la expresión lambda puede tomar un parámetro
- De manera opcional, puede aceptar los siguientes parámetros antes de la expresión lambda:
db: RoomDatabase: Accede a la instancia de la base de datos, lo que resulta útil para obtener el alcance de la corrutina o realizar operaciones adicionales.tableNames: Array<String>oList<String>: Proporciona los nombres de las tablas a las que accede la búsqueda, lo que resulta útil para los tipos observables.rawQuery: RoomRawQuery: Proporciona la instancia de tiempo de ejecución de la búsqueda.inTransaction: Boolean: Indica si la búsqueda se está ejecutando dentro de una transacción.