Per evitare che le query blocchino l'interfaccia utente, Room non supporta l'accesso al database sul thread principale. Questa limitazione significa che devi rendere asincrone le query DAO. La libreria Room include integrazioni con diversi framework per fornire l'esecuzione di query asincrone.
Le query DAO rientrano in tre categorie:
- Query di scrittura one-shot che inseriscono, aggiornano o eliminano dati nel database.
- Query di lettura one-shot che leggono i dati dal database una sola volta e restituiscono un risultato con lo snapshot del database in quel momento.
- Query di lettura osservabile che leggono i dati dal database ogni volta che le tabelle di database sottostanti cambiano ed emettono nuovi valori per riflettere queste modifiche.
Opzioni di linguaggio e framework
Room fornisce il supporto per l'integrazione per l'interoperabilità con funzionalità e librerie di linguaggi specifici. La tabella seguente mostra i tipi di restituzione applicabili in base al tipo di query e al framework:
| Tipo di query | Funzionalità del linguaggio Kotlin (nativo) | RxJava | Guava | Jetpack Lifecycle* |
|---|---|---|---|---|
| Scrittura one-shot | Coroutine (suspend) |
Single<T>, Maybe<T>,
Completable |
ListenableFuture<T> |
N/D |
| Lettura one-shot | Coroutine (suspend) |
Single<T>, Maybe<T> |
ListenableFuture<T> |
N/D |
| Lettura osservabile | Flow<T> |
Flowable<T>, Publisher<T>,
Observable<T> |
N/D | LiveData<T> |
Questa guida illustra tre modi per utilizzare queste integrazioni per implementare query asincrone nei DAO.
Kotlin con Flow e coroutine
Kotlin fornisce funzionalità di linguaggio integrate che consentono di scrivere query asincrone senza framework di terze parti:
- Room supporta direttamente Flow di Kotlin per scrivere query osservabili.
- Room richiede la parola chiave
suspendper rendere asincrone le query DAO one-shot con le coroutine Kotlin.
Il supporto per coroutine e Flow è integrato direttamente nel runtime principale di Room, quindi non sono necessari artefatti aggiuntivi.
RxJava per Kotlin e Java
Room 3.0 supporta i tipi di restituzione RxJava 3. Per utilizzare i tipi restituiti RxJava, devi registrare i convertitori di tipi restituiti RxJava nel database o nel DAO:
- Includi l'artefatto
androidx.room3:room3-rxjava3nella configurazione della build. - Annota la dichiarazione
@Databaseo@Daocon@DaoReturnTypeConverters(RxDaoReturnTypeConverters::class).
Room supporta i seguenti tipi di restituzione RxJava 3:
- Query one-shot:
Completable,Single<T>, eMaybe<T> - Query osservabili:
Publisher<T>,Flowable<T>, eObservable<T>
LiveData e Guava
Room 3.0 supporta i tipi di restituzione LiveData e Guava ListenableFuture utilizzando i convertitori:
- LiveData: includi l'artefatto
androidx.room3:room3-livedatae annota il database o il DAO con@DaoReturnTypeConverters(LiveDataDaoReturnTypeConverter::class). - Guava: includi l'artefatto
androidx.room3:room3-guavae annota il database o il DAO con@DaoReturnTypeConverters(GuavaDaoReturnTypeConverter::class).
Scrivere query one-shot asincrone
Le query one-shot sono operazioni di database che vengono eseguite una sola volta e acquisiscono uno snapshot dei dati al momento dell'esecuzione. Ecco alcuni esempi di query one-shot asincrone:
@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> }
Scrivere query osservabili
Le query osservabili sono operazioni di lettura che emettono nuovi valori ogni volta che le tabelle a cui fanno riferimento cambiano. Ad esempio, puoi utilizzare questo comportamento per mantenere aggiornato un elenco di elementi visualizzato man mano che il database cambia. Ecco alcuni esempi di query osservabili:
@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>> }
Monitorare manualmente l'invalidazione del database
Quando devi creare manualmente operazioni di database osservabili, puoi utilizzare l'
createFlow API di InvalidationTracker. Questa API consente di creare un Flow che monitora le modifiche a tabelle specifiche ed emette una notifica ogni volta che queste tabelle cambiano.
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) } }
Per impostazione predefinita, il Flow restituito emette un valore iniziale contenente tutte le tabelle registrate per avviare lo stream. Puoi disattivare questo comportamento impostando il parametro emitInitialState su false.
Convertitori di tipi restituiti DAO personalizzati
Per i tipi non supportati direttamente da Room o dalle relative librerie di estensione, puoi definire convertitori di tipi restituiti DAO personalizzati per supportare tipi restituiti aggiuntivi. Per trasformare il risultato di una funzione DAO nel tuo tipo personalizzato,
annota una funzione di conversione con @DaoReturnTypeConverter.
Ad esempio, puoi definire un convertitore che utilizza androidx.tracing per aggiungere sezioni di traccia intorno all'esecuzione di una query per monitorare le query sensibili alle prestazioni eseguendo il wrapping dell'esecuzione in un tipo TracedQuery personalizzato:
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) } }
Per utilizzare il convertitore, annota il database o il DAO con
@DaoReturnTypeConverters:
@Dao @DaoReturnTypeConverters(TracingDaoReturnTypeConverter::class) interface MusicDao { @Query("SELECT * FROM Song") suspend fun getAllSongs(): TracedQuery<List<Song>> }
Controllare l'inizializzazione del convertitore di tipi di restituzione DAO
In genere, Room gestisce l'istanziazione dei convertitori di tipi restituiti DAO.
Tuttavia, se devi passare dipendenze aggiuntive alle classi di conversione, la tua app deve controllare direttamente la loro inizializzazione. In questo caso, annota la classe di conversione 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) } }
Poi, oltre a dichiarare la classe di conversione in
@DaoReturnTypeConverters, utilizza la
RoomDatabase.Builder.addDaoReturnTypeConverter funzione per passare un'istanza della classe di conversione al RoomDatabase builder:
val db = Room.databaseBuilder<MyDatabase>(applicationContext, "database-name") .addDaoReturnTypeConverter(TracingDaoReturnTypeConverter(myLoggerInstance)) .build()
Requisiti della funzione di conversione
Una funzione @DaoReturnTypeConverter deve soddisfare diversi requisiti:
- Deve avere un parametro funzionale come ultimo argomento, in genere denominato
executeAndConvert. Questo parametro è una lambdasuspendche Room genera per eseguire la query e analizzare il risultato.- Se il convertitore deve trasformare la query, ad esempio Paging, la lambda può accettare un parametro
RoomRawQuery.
- Se il convertitore deve trasformare la query, ad esempio Paging, la lambda può accettare un parametro
- Può accettare facoltativamente i seguenti parametri prima della lambda:
db: RoomDatabase: accede all'istanza del database, utile per ottenere l'ambito della coroutine o eseguire operazioni aggiuntive.tableNames: Array<String>oList<String>: fornisce i nomi delle tabelle a cui accede la query, utile per i tipi osservabili.rawQuery: RoomRawQuery: fornisce l'istanza di runtime della query.inTransaction: Boolean: indica se la query viene eseguita all'interno di una transazione.