Para impedir que as consultas bloqueiem a interface, o Room não oferece suporte ao acesso ao banco de dados na linha de execução principal. Devido a essa restrição, é necessário fazer com que as consultas DAO assíncronas. A biblioteca do Room inclui integrações com vários frameworks para oferecer a execução de consulta assíncrona.
As consultas DAO se enquadram em três categorias:
- Consultas de gravação única, que inserem, atualizam ou excluem dados do banco de dados.
- Consultas de leitura única, que leem dados do banco de dados apenas uma vez e retornam um resultado com um snapshot do banco de dados naquele momento.
- Consultas de leitura observável, que leem dados do banco de dados sempre que as tabelas subjacentes mudam e emitem novos valores para refletir essas mudanças.
Opções de linguagem e framework
O Room oferece suporte de integração para interoperabilidade com bibliotecas e recursos de linguagem específicos. A tabela abaixo mostra os tipos de retorno aplicáveis de acordo com o tipo de consulta e framework:
| Tipo de consulta | Recursos da linguagem Kotlin (nativo) | RxJava | Goiaba | Ciclo de vida do Jetpack* |
|---|---|---|---|---|
| Gravação única | Corrotinas (suspend) |
Single<T>, Maybe<T>,
Completable |
ListenableFuture<T> |
N/A |
| Leitura única | Corrotinas (suspend) |
Single<T>, Maybe<T> |
ListenableFuture<T> |
N/A |
| Leitura observável | Flow<T> |
Flowable<T>, Publisher<T>,
Observable<T> |
N/A | LiveData<T> |
Este guia demonstra três maneiras de usar essas integrações para implementar consultas assíncronas nos DAOs.
Kotlin com fluxo e corrotinas
O Kotlin oferece recursos de linguagem integrados que permitem criar consultas assíncronas sem frameworks de terceiros:
- O Room oferece suporte direto ao fluxo do Kotlin para criar consultas observáveis.
- O Room exige a palavra-chave
suspendpara tornar as consultas DAO únicas assíncronas com corrotinas do Kotlin.
O suporte a corrotinas e fluxo é integrado diretamente ao ambiente de execução principal do Room, portanto, nenhum artefato adicional é necessário.
RxJava para Kotlin e Java
O Room 3.0 oferece suporte a tipos de retorno RxJava 3. Para usar os tipos de retorno RxJava, registre os conversores de tipo de retorno RxJava no banco de dados ou DAO:
- Inclua o artefato
androidx.room3:room3-rxjava3na configuração de build. - Adicione a anotação
@DaoReturnTypeConverters(RxDaoReturnTypeConverters::class)à declaração@Databaseou@Dao.
O Room oferece suporte a estes tipos de retorno RxJava 3:
- Consultas únicas:
Completable,Single<T>, eMaybe<T> - Consultas observáveis:
Publisher<T>,Flowable<T>, eObservable<T>
LiveData e Guava
O Room 3.0 oferece suporte a tipos de retorno LiveData e Guava ListenableFuture usando conversores:
- LiveData: inclua o artefato
androidx.room3:room3-livedatae adicione a anotação@DaoReturnTypeConverters(LiveDataDaoReturnTypeConverter::class)ao banco de dados ou DAO. - Guava: inclua o artefato
androidx.room3:room3-guavae adicione a anotação ao banco de dados ou DAO com@DaoReturnTypeConverters(GuavaDaoReturnTypeConverter::class).
Criar consultas assíncronas únicas
Consultas únicas são operações de banco de dados que são executadas apenas uma vez e capturam um snapshot dos dados durante a execução. Veja abaixo alguns exemplos de consultas assí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> }
Criar consultas observáveis
Consultas observáveis são operações de leitura que emitem novos valores sempre que as tabelas referenciadas mudam. Por exemplo, é possível usar esse comportamento para manter uma lista de itens exibida atualizada à medida que o banco de dados muda. Veja abaixo alguns exemplos de consultas observáveis:
@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>> }
Rastrear a invalidação do banco de dados manualmente
Quando você precisa criar operações de banco de dados observáveis manualmente, é possível usar a
createFlow API de InvalidationTracker. Essa API permite criar um Flow que rastreia modificações em tabelas específicas e emite uma notificação sempre que essas tabelas mudam.
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) } }
Por padrão, o Flow retornado emite um valor inicial contendo todas as tabelas registradas para iniciar o fluxo. É possível desativar esse comportamento definindo o parâmetro emitInitialState como false.
Conversores de tipo de retorno DAO personalizados
Para tipos que não são compatíveis diretamente com o Room ou as bibliotecas de extensão, é possível definir conversores de tipo de retorno DAO personalizados para oferecer suporte a outros tipos de retorno. Para transformar o resultado de uma função DAO no tipo personalizado,
adicione a anotação @DaoReturnTypeConvertera uma função de conversor.
Por exemplo, é possível definir um conversor que usa androidx.tracing para adicionar seções de rastreamento em torno da execução de uma consulta para monitorar consultas sensíveis ao desempenho, encapsulando a execução em um 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 o conversor, adicione a anotação ao banco de dados ou DAO com
@DaoReturnTypeConverters:
@Dao @DaoReturnTypeConverters(TracingDaoReturnTypeConverter::class) interface MusicDao { @Query("SELECT * FROM Song") suspend fun getAllSongs(): TracedQuery<List<Song>> }
Controlar a inicialização do conversor de tipo de retorno DAO
Geralmente, o Room processa a instanciação de conversores de tipo de retorno DAO.
No entanto, se você precisar transmitir outras dependências para as classes de conversores, o app precisará controlar diretamente a inicialização delas. Nesse caso, adicione a anotação @ProvidedDaoReturnTypeConverter à classe de conversor:
@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) } }
Em seguida, além de declarar a classe de conversor em
@DaoReturnTypeConverters, use a
RoomDatabase.Builder.addDaoReturnTypeConverter função para transmitir uma
instância da classe de conversor ao RoomDatabase builder:
val db = Room.databaseBuilder<MyDatabase>(applicationContext, "database-name") .addDaoReturnTypeConverter(TracingDaoReturnTypeConverter(myLoggerInstance)) .build()
Requisitos da função de conversor
Uma função @DaoReturnTypeConverter precisa atender a vários requisitos:
- Ela precisa ter um parâmetro funcional como último argumento, geralmente chamado de
executeAndConvert. Esse parâmetro é uma lambdasuspendque o Room gera para executar a consulta e analisar o resultado.- Se o conversor precisar transformar a consulta, como a paginação, a lambda poderá usar um parâmetro
RoomRawQuery.
- Se o conversor precisar transformar a consulta, como a paginação, a lambda poderá usar um parâmetro
- Ela pode aceitar opcionalmente os seguintes parâmetros antes da lambda:
db: RoomDatabase: acessa a instância do banco de dados, o que é útil para receber o escopo da corrotina ou realizar outras operações.tableNames: Array<String>ouList<String>: fornece os nomes das tabelas acessadas pela consulta, o que é útil para tipos observáveis.rawQuery: RoomRawQuery: fornece a instância de execução da consulta.inTransaction: Boolean: indica se a consulta está sendo executada em uma transação.