Pour empêcher les requêtes de bloquer l'interface utilisateur, Room ne permet pas d'accéder à la base de données sur le thread principal. Cette restriction signifie que vous devez rendre vos requêtes DAO asynchrones. La bibliothèque Room inclut des intégrations avec plusieurs frameworks pour permettre l'exécution de requêtes asynchrones.
Les requêtes DAO appartiennent à trois catégories :
- Requêtes d'écriture unique qui insèrent, mettent à jour ou suppriment des données dans la base de données.
- Requêtes de lecture unique qui lisent les données de votre base de données une seule fois et renvoient un résultat avec l'instantané de la base de données à ce moment-là.
- Requêtes de lecture observables qui lisent les données de votre base de données chaque fois que les tables de base de données sous-jacentes changent et émettent de nouvelles valeurs reflétant ces modifications.
Options de langage et de framework
Room permet l'interopérabilité avec des bibliothèques et des fonctionnalités de langage spécifiques. Le tableau suivant présente les types renvoyés applicables en fonction du type de requête et du framework :
| Type de requête | Fonctionnalités du langage Kotlin (natif) | RxJava | Guava | Jetpack Lifecycle* |
|---|---|---|---|---|
| Écriture unique | Coroutines (suspend) |
Single<T>, Maybe<T>,
Completable |
ListenableFuture<T> |
N/A |
| Lecture unique | Coroutines (suspend) |
Single<T>, Maybe<T> |
ListenableFuture<T> |
N/A |
| Lecture observable | Flow<T> |
Flowable<T>, Publisher<T>,
Observable<T> |
N/A | LiveData<T> |
Ce guide présente trois façons d'utiliser ces intégrations pour implémenter des requêtes asynchrones dans vos DAO.
Kotlin avec Flow et coroutines
Kotlin fournit des fonctionnalités de langage intégrées qui vous permettent d'écrire des requêtes asynchrones sans frameworks tiers :
- Room est directement compatible avec Flow de Kotlin pour écrire des requêtes observables.
- Room nécessite le mot clé
suspendpour rendre vos requêtes DAO uniques asynchrones avec les coroutines Kotlin.
La compatibilité avec les coroutines et Flow est intégrée directement dans l'environnement d'exécution Room de base. Aucun artefact supplémentaire n'est donc requis.
RxJava pour Kotlin et Java
Room 3.0 est compatible avec les types renvoyés RxJava 3. Pour utiliser les types renvoyés RxJava, vous devez enregistrer les convertisseurs de types renvoyés RxJava dans votre base de données ou votre DAO :
- Incluez l'artefact
androidx.room3:room3-rxjava3dans votre configuration de compilation. - Annotez votre
@Databaseou@Daodéclaration avec@DaoReturnTypeConverters(RxDaoReturnTypeConverters::class).
Room est compatible avec les types renvoyés RxJava 3 suivants :
- Requêtes uniques:
Completable,Single<T>, etMaybe<T> - Requêtes observables:
Publisher<T>,Flowable<T>, etObservable<T>
LiveData et Guava
Room 3.0 est compatible avec les types renvoyés LiveData et Guava ListenableFuture à l'aide de convertisseurs :
- LiveData : incluez l'artefact
androidx.room3:room3-livedataet annotez votre base de données ou votre DAO avec@DaoReturnTypeConverters(LiveDataDaoReturnTypeConverter::class). - Guava : incluez l'artefact
androidx.room3:room3-guavaet annotez votre base de données ou votre DAO avec@DaoReturnTypeConverters(GuavaDaoReturnTypeConverter::class).
Écrire des requêtes uniques asynchrones
Les requêtes uniques sont des opérations de base de données qui ne s'exécutent qu'une seule fois et récupèrent un instantané des données au moment de l'exécution. Voici quelques exemples de requêtes uniques asynchrones :
@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> }
Écrire des requêtes observables
Les requêtes observables sont des opérations de lecture qui émettent de nouvelles valeurs chaque fois que les tables référencées changent. Vous pouvez, par exemple, utiliser ce comportement pour maintenir à jour la liste des éléments affichés à mesure que la base de données change. Voici quelques exemples de requêtes 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>> }
Effectuer manuellement le suivi de l'invalidation de la base de données
Lorsque vous devez créer manuellement des opérations de base de données observables, vous pouvez utiliser l'
createFlow API de InvalidationTracker. Cette API vous permet de créer un Flow qui suit les modifications apportées à des tables spécifiques et émet une notification chaque fois que ces tables changent.
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) } }
Par défaut, le Flow renvoyé émet une valeur initiale contenant toutes les tables enregistrées pour lancer le flux. Vous pouvez désactiver ce comportement en définissant le paramètre emitInitialState sur false.
Convertisseurs de types renvoyés DAO personnalisés
Pour les types qui ne sont pas directement compatibles avec Room ou ses bibliothèques d'extension, vous pouvez définir des convertisseurs de types renvoyés DAO personnalisés pour prendre en charge des types renvoyés supplémentaires. Pour transformer le résultat d'une fonction DAO en type personnalisé,
annotez une fonction de convertisseur avec @DaoReturnTypeConverter.
Par exemple, vous pouvez définir un convertisseur qui utilise androidx.tracing pour ajouter des sections de trace autour de l'exécution d'une requête afin de surveiller les requêtes sensibles aux performances en encapsulant l'exécution dans un type TracedQuery personnalisé :
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) } }
Pour utiliser le convertisseur, annotez votre base de données ou votre DAO avec
@DaoReturnTypeConverters :
@Dao @DaoReturnTypeConverters(TracingDaoReturnTypeConverter::class) interface MusicDao { @Query("SELECT * FROM Song") suspend fun getAllSongs(): TracedQuery<List<Song>> }
Contrôler l'initialisation du convertisseur de type renvoyé DAO
Généralement, Room gère l'instanciation des convertisseurs de types renvoyés DAO.
Toutefois, si vous devez transmettre des dépendances supplémentaires à vos classes de convertisseur, votre application doit contrôler directement leur initialisation. Dans ce cas, annotez la classe de vos
convertisseurs avec @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) } }
Ensuite, en plus de déclarer la classe de vos convertisseurs dans
@DaoReturnTypeConverters, utilisez la
RoomDatabase.Builder.addDaoReturnTypeConverter fonction pour transmettre une
instance de cette classe à RoomDatabase :
val db = Room.databaseBuilder<MyDatabase>(applicationContext, "database-name") .addDaoReturnTypeConverter(TracingDaoReturnTypeConverter(myLoggerInstance)) .build()
Conditions requises pour la fonction de convertisseur
Une fonction @DaoReturnTypeConverter doit répondre à plusieurs exigences :
- Elle doit avoir un paramètre fonctionnel comme dernier argument, généralement nommé
executeAndConvert. Ce paramètre est un lambdasuspendque Room génère pour exécuter la requête et analyser le résultat.- Si le convertisseur doit transformer la requête, par exemple la pagination, le lambda peut accepter un paramètre
RoomRawQuery.
- Si le convertisseur doit transformer la requête, par exemple la pagination, le lambda peut accepter un paramètre
- Il peut éventuellement accepter les paramètres suivants avant le lambda :
db: RoomDatabase: accède à l'instance de base de données, ce qui est utile pour obtenir le champ d'application de la coroutine ou effectuer des opérations supplémentaires.tableNames: Array<String>ouList<String>: fournit les noms des tables auxquelles la requête accède, ce qui est utile pour les types observables.rawQuery: RoomRawQuery: fournit l'instance d'exécution de la requête.inTransaction: Boolean: indique si la requête s'exécute dans une transaction.