7 conseils utiles pour utiliser la piĂšce

7 conseils utiles pour utiliser la piĂšce


Room est une couche d'abstraction au-dessus de SQLite qui simplifie le stockage. Si vous ĂȘtes nouveau dans Room, consultez cet article d'introduction:


7 étapes pour utiliser Room. Procédure pas à pas pour la migration de votre application vers Room

Et dans cet article, je voudrais partager quelques conseils sur la façon d'utiliser Room aussi efficacement que possible.


1. Préremplir la base de données


Devez-vous ajouter des données par défaut à votre base de données immédiatement aprÚs sa création ou au moment du premier accÚs? Utilisez RoomDatabase # Callback . Appelez la méthode addCallback lors de la création de votre base de données et remplacez onCreate ou onOpen .


onCreate sera appelé lors de la premiÚre création de la base de données, immédiatement aprÚs la création des tables. onOpen est appelé lorsque la base de données est ouverte. Puisque l'accÚs au DAO n'est possible qu'aprÚs l'achÚvement de ces méthodes, nous créons un nouveau flux dans lequel nous obtenons un lien vers la base de données, puis nous obtenons le DAO et insérons les données nécessaires.


 Room.databaseBuilder(context.applicationContext, DataDatabase::class.java, "Sample.db") // prepopulate the database after onCreate was called .addCallback(object : Callback() { override fun onCreate(db: SupportSQLiteDatabase) { super.onCreate(db) // moving to a new thread ioThread { getInstance(context).dataDao() .insert(PREPOPULATE_DATA) } } }) .build() 

Voir l'exemple complet ici .


Remarque: si vous utilisez l'approche ioThread et que l'application se bloque au premier démarrage, entre la création de la base de données et l'insertion, les données ne seront jamais insérées.


2. Utilisation des capacités d'héritage DAO


Avez-vous plusieurs tables dans votre base de donnĂ©es et copiez-vous les mĂȘmes mĂ©thodes d' insertion , de mise Ă  jour et de suppression ? Les DAO prennent en charge l'hĂ©ritage, alors crĂ©ez une BaseDao<T> et dĂ©finissez-y vos mĂ©thodes courantes @Insert , @Update et @Delete . Laissez chaque DAO Ă©tendre BaseDao et ajoutez des mĂ©thodes spĂ©cifiques Ă  chacun d'eux.


 interface BaseDao<T> { @Insert fun insert(vararg obj: T) } @Dao abstract class DataDao : BaseDao<Data>() { @Query("SELECT * FROM Data") abstract fun getData(): List<Data> } 

Voir les détails ici .


Les DAO doivent ĂȘtre des interfaces ou des classes abstraites car Room gĂ©nĂšre leur implĂ©mentation au moment de la compilation, y compris les mĂ©thodes de BaseDao .


3. ExĂ©cution de requĂȘtes dans les transactions sans code passe-partout


L'annotation d'une mĂ©thode Ă  l'aide de @Transaction garantit que toutes les opĂ©rations de base de donnĂ©es que vous effectuez dans cette mĂ©thode sont exĂ©cutĂ©es dans la mĂȘme transaction. Une transaction ne sera pas exĂ©cutĂ©e si une exception se produit dans le corps de la mĂ©thode.


 @Dao abstract class UserDao { @Transaction open fun updateData(users: List<User>) { deleteAllUsers() insertAll(users) } @Insert abstract fun insertAll(users: List<User>) @Query("DELETE FROM Users") abstract fun deleteAllUsers() } 

Vous souhaiterez peut-ĂȘtre utiliser l'annotation @Transaction pour les mĂ©thodes @Transaction qui utilisent l'instruction select dans les cas:


  • Lorsque le rĂ©sultat de la requĂȘte est assez volumineux. En effectuant une demande en une seule transaction, vous garantissez que si le rĂ©sultat de la demande ne tient pas dans une "partie" du curseur, il ne sera pas endommagĂ© en raison des changements dans la base de donnĂ©es entre les permutations du curseur .
  • Lorsque le rĂ©sultat de la requĂȘte est un POJO avec des champs @Relation . Chaque champ est une demande en soi, donc les exĂ©cuter en une seule transaction garantit des rĂ©sultats cohĂ©rents entre les demandes.

Les @Delete , @Update et @Insert , qui ont plusieurs paramÚtres, sont automatiquement lancées dans la transaction.


4. Lire uniquement ce dont vous avez besoin


Lorsque vous faites une demande à la base de données, utilisez-vous tous les champs que vous obtenez dans la réponse? Prenez soin de la quantité de mémoire utilisée par votre application et téléchargez uniquement les champs que vous utiliserez finalement. Cela augmentera également la vitesse de vos demandes en réduisant le coût des E / S. Room fera le mapping entre les colonnes et l'objet pour vous.


Considérez cet objet User complexe:


 @Entity(tableName = "users") data class User(@PrimaryKey val id: String, val userName: String, val firstName: String, val lastName: String, val email: String, val dateOfBirth: Date, val registrationDate: Date) 

Sur certains écrans, nous n'avons pas besoin d'afficher toutes ces informations. Ainsi, à la place, nous pouvons créer un objet UserMinimal qui contient uniquement les données nécessaires.


 data class UserMinimal(val userId: String, val firstName: String, val lastName: String) 

Dans la classe DAO, nous dĂ©finissons la requĂȘte et sĂ©lectionnons les colonnes correctes dans la table des users .


 @Dao interface UserDao { @Query(“SELECT userId, firstName, lastName FROM Users) fun getUsersMinimal(): List<UserMinimal> } 

5. ContrÎle des dépendances entre les entités avec des clés étrangÚres


MĂȘme si Room ne prend pas directement en charge les relations entre les entitĂ©s, il vous permet de dĂ©finir des dĂ©pendances entre des objets Ă  l'aide de clĂ©s Ă©trangĂšres.


Room a l'annotation @ForeignKey , qui fait partie de l'annotation @Entity . En termes de fonctionnalité, il est similaire aux clés étrangÚres dans SQLite . Il garantit la préservation des relations entre les entités lorsque des modifications sont apportées à la base de données. Pour l'ajouter, définissez l'objet à référencer, ainsi que les colonnes de l'objet courant et le volume auquel vous faites référence.


Considérez la classe User et Pet . Pet a un propriétaire - l'ID utilisateur référencé par la clé étrangÚre.


 @Entity(tableName = "pets", foreignKeys = arrayOf( ForeignKey(entity = User::class, parentColumns = arrayOf("userId"), childColumns = arrayOf("owner")))) data class Pet(@PrimaryKey val petId: String, val name: String, val owner: String) 

Si vous le souhaitez, vous pouvez dĂ©terminer l'action Ă  entreprendre lorsque le parent est supprimĂ© ou mis Ă  jour dans la base de donnĂ©es. Vous pouvez choisir l'une des options suivantes: NO_ACTION , RESTRICT , SET_NULL , SET_DEFAULT ou CASCADE , qui se comportent de la mĂȘme maniĂšre que dans SQLite .


Remarque: dans Room, SET_DEFAULT fonctionne comme SET_NULL , car Room ne vous permet pas encore de définir des valeurs par défaut pour les colonnes.


6. Simplifiez les requĂȘtes un-Ă -plusieurs avec @Relation


Dans l'exemple précédent User - Pet , nous pouvons dire qu'il existe une relation un-à-plusieurs : l'utilisateur peut avoir plusieurs animaux. Supposons que nous voulons obtenir une liste d'utilisateurs avec leurs animaux de compagnie: List<UserAndAllPets> .


 data class UserAndAllPets (val user: User, val pets: List<Pet> = ArrayList()) 

Pour ce faire manuellement, nous avons besoin de 2 requĂȘtes: l'une pour obtenir une liste de tous les utilisateurs et l'autre pour obtenir une liste d'animaux de compagnie en fonction de l'ID utilisateur.


 @Query(“SELECT * FROM Users”) public List<User> getUsers(); @Query(“SELECT * FROM Pets where owner = :userId”) public List<Pet> getPetsForUser(String userId); 

Ensuite, nous allons parcourir la liste des utilisateurs et accéder à la table Pets chaque fois.


L' annotation @Relation nous facilitera la vie: elle demandera automatiquement les objets associĂ©s. @Relation ne peut ĂȘtre appliquĂ© qu'Ă  List ou Set . Mettez Ă  jour la classe UserAndAllPets:


 class UserAndAllPets { @Embedded var user: User? = null @Relation(parentColumn = “userId”, entityColumn = “owner”) var pets: List<Pet> = ArrayList() } 

Dans le DAO, nous dĂ©finissons une requĂȘte, et Room interrogera les tables Users and Pets et mappera les objets de maniĂšre autonome.


 @Transaction @Query(“SELECT * FROM Users”) List<UserAndAllPets> getUsers(); 

7. Éviter les fausses notifications pour les demandes observables


Supposons que vous souhaitiez obtenir un utilisateur par son identifiant Ă  l'aide d'une requĂȘte observable:


 @Query(“SELECT * FROM Users WHERE userId = :id) fun getUserById(id: String): LiveData<User> // or @Query(“SELECT * FROM Users WHERE userId = :id) fun getUserById(id: String): Flowable<User> 

Vous recevrez un nouvel objet User chaque mise Ă  jour. Mais vous recevrez Ă©galement cet objet lorsque d'autres modifications se produisent dans la table Users (suppressions, mises Ă  jour ou insertions) qui n'ont rien Ă  voir avec l'utilisateur qui vous intĂ©resse, ce qui entraĂźnera de fausses notifications. De plus, si votre requĂȘte comprend plusieurs tableaux, vous recevrez de nouveaux messages chaque fois que quelque chose change dans l'un d'eux.


Voici ce qui se passe dans les coulisses:


  1. SQLite a des déclencheurs qui se déclenchent chaque fois que DELETE , UPDATE ou INSERT se produit dans une table.
  2. Room crée un InvalidationTracker qui utilise des Observers qui suivent toutes les modifications dans les tables observées.
  3. Les demandes LiveData et LiveData reposent toutes deux sur la notification InvalidationTracker.Observer # onInvalidated . Lorsqu'il est reçu, une demande répétée se produit.

La chambre sait seulement que la table a Ă©tĂ© changĂ©e, mais ne sait pas pourquoi ni ce qui a changĂ©. Par consĂ©quent, aprĂšs une deuxiĂšme demande, le rĂ©sultat de la demande est transmis Ă  l'aide de LiveData ou LiveData . Parce que La piĂšce ne stocke aucune donnĂ©e en mĂ©moire; elle ne peut pas dĂ©terminer s'il s'agit des mĂȘmes donnĂ©es ou non.


Vous devez vous assurer que votre DAO filtre les demandes et ne répond qu'aux objets nécessaires.


Si la requĂȘte observable est implĂ©mentĂ©e Ă  l'aide de Flowables , utilisez Flowable # diverUntilChanged .


 @Dao abstract class UserDao : BaseDao<User>() { /** * Get a user by id. * @return the user from the table with a specific id. */ @Query(“SELECT * FROM Users WHERE userid = :id”) protected abstract fun getUserById(id: String): Flowable<User> fun getDistinctUserById(id: String): Flowable<User> = getUserById(id) .distinctUntilChanged() } 

Si votre requĂȘte renvoie LiveData , vous pouvez utiliser MediatorLiveData , qui ne recevra que les objets souhaitĂ©s de la source.


 fun <T> LiveData<T>.getDistinct(): LiveData<T> { val distinctLiveData = MediatorLiveData<T>() distinctLiveData.addSource(this, object : Observer<T> { private var initialized = false private var lastObj: T? = null override fun onChanged(obj: T?) { if (!initialized) { initialized = true lastObj = obj distinctLiveData.postValue(lastObj) } else if ((obj == null && lastObj != null) || obj != lastObj) { lastObj = obj distinctLiveData.postValue(lastObj) } } }) return distinctLiveData } 

Dans vos DAO, la méthode qui retourne LiveData est public et la méthode qui interroge la base de données est protected .


 @Dao abstract class UserDao : BaseDao<User>() { @Query(“SELECT * FROM Users WHERE userid = :id”) protected abstract fun getUserById(id: String): LiveData<User> fun getDistinctUserById(id: String): LiveData<User> = getUserById(id).getDistinct() } 

Voir l'exemple de code complet ici .


Remarque: si vous demandez une liste à afficher, faites attention à la bibliothÚque de pagination , qui renverra un LivePagedListBuilder . La bibliothÚque vous aidera à calculer automatiquement la différence entre les éléments de la liste et à mettre à jour votre interface utilisateur.


Voir aussi: 7 étapes pour utiliser Room. Procédure pas à pas pour la migration de votre application vers Room

Source: https://habr.com/ru/post/fr442786/


All Articles