Les informations sont probablement la ressource la plus importante à laquelle les utilisateurs font confiance avec les apps. Pour les développeurs d’apps, ces informations nous disent qui est l’utilisateur, ce qui nous donne le pouvoir de fournir une bonne expérience utilisateur (UX). De plus, en appliquant des règles commerciales à ces informations, nous définissons le comportement que l’application doit avoir. Ces informations peuvent être sensibles et exposer les données privées des utilisateurs, il est donc très important que nous les traitions correctement pour garantir leur intégrité, leur confidentialité et leur stockage adéquat. Dans ce tutoriel Android Room, nous allons vous montrer comment vous pouvez travailler avec ces données plus facilement, tout en assurant leur intégrité et leur sécurité, le tout en utilisant Room. Room fait partie des composants de l’architecture Android.
Avez-vous besoin de Room?
Lorsque nous parlons de stocker des informations de manière persistante sur Android, l’option la plus importante et la plus « facile » est d’utiliser SQLite simple. Cependant, pour avoir une bonne implémentation de base de données utilisant SQLite implique la génération de beaucoup de code qui n’apporte pas de réelle valeur. L’architecture qui suit n’est souvent pas aussi propre ou claire qu’elle pourrait l’être.
Vous pourriez également utiliser le mapping objet-relationnel, ou ORM, mais vous devrez toujours définir et créer manuellement la base de données, en sous-classant SQLiteOpenHelper et en créant les classes de contrat.
Donc, la question est : existe-t-il un moyen de simplifier tout ce processus ? La réponse est : oui, la salle est votre solution.
Un regard plus attentif sur Room et son fonctionnement
Room est l’un des outils les plus importants des composants architecturaux Android. Sorti lors de la Google I/O 2016, c’est un outil puissant pour stocker et manipuler des informations sur les applications Android. Il fournit un moyen très facile de travailler avec les données et assure toujours leur sécurité et leur intégrité.
Room n’est pas un ORM ; c’est plutôt toute une bibliothèque qui nous permet de créer et de manipuler plus facilement les bases de données SQLite. En utilisant des annotations, nous pouvons définir nos bases de données, nos tables et nos opérations. Room traduira automatiquement ces annotations en instructions/requêtes SQLite pour effectuer les opérations correspondantes dans le moteur de base de données.
Les trois composants majeurs de Room sont :
– Entité : Représente une table au sein de la base de données Room. Elle doit être annotée avec @Entity.
– DAO : Une interface qui contient les méthodes d’accès à la base de données. Elle est annotée avec @Dao.
– Database : Représente la base de données. C’est un objet qui détient une connexion à la base de données SQLite, et toutes les opérations sont exécutées à travers elle. Il est annoté avec @Database.
L’architecture de la salle ressemble à ceci :
Too Much Talk-Let’s Look at an Example
Plongeons-nous dans ce tutoriel Android Room. Imaginez que nous devons créer une application pour stocker votre routine de gym. Nous allons avoir quatre entités dans notre base de données, comme nous allons le montrer ci-dessous. Tout le code de l’échantillon est écrit en utilisant Kotlin (si vous ne connaissez pas Kotlin ou si vous voulez en savoir plus, je vous invite à lire mon article à ce sujet).
La première chose que nous devons faire est de mettre à jour notre fichier gradle. Il devrait ressembler à ceci :
apply plugin: 'com.android.application'apply plugin: 'kotlin-android'apply plugin: 'kotlin-android-extensions'apply plugin: 'kotlin-kapt'android { //.. Omitted since it is not relevant for the example}dependencies { //... some dependencies were omitted due to they are not relevant for the example def room_version = "2.2.0-rc01" implementation "androidx.room:room-runtime:$room_version" kapt "androidx.room:room-compiler:$room_version" compile 'com.google.code.gson:gson:2.2.4' implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' implementation 'io.reactivex.rxjava2:rxjava:2.1.17'}
Voyons et analysons chacun des trois composants majeurs de Room : Entités, DAOs, et Base de données.
Entités
Pour notre exemple, nous allons utiliser quatre entités : Sexe, Exercice, Routine, et Stagiaire.
Sexe.kt
Représente le sexe du stagiaire
@Entitydata class Gender( @PrimaryKey(autoGenerate = true) val id: Int? = null, val name: String)
Ce qu’il faut noter :
– Toutes les classes qui représentent une entité de la base de données doivent être annotées avec @Entity
– Avec l’annotation @PrimaryKey(autoGenerate = true), nous indiquons que l’id est la clé primaire de l’entité et doit être autoGénérée par le moteur de la base de données.
Exercise.kt
Représente un exercice qui fait partie d’une routine.
@Entitydata class Exercise( @PrimaryKey(autoGenerate = true) val exerciseId: Int, val name: String, val repetitions:Int, @ColumnInfo(name = "machine_name") val machineName: String, val liftedWeight: Int)
Ce qu’il faut noter :
– Par défaut, Room utilise les noms de champs comme noms de colonnes dans la base de données. Si vous voulez qu’une colonne ait un nom différent, ajoutez l’annotation @ColumnInfo à un champ.
Routine.kt
Basiquement un conteneur d’exercices qui, ensemble, créent une routine d’exercices.
@Entity(tableName = "traineeRoutine")data class Routine( @PrimaryKey(autoGenerate = true) val routineId: Int, @ColumnInfo(name = "due_day") val dueDay: Date, @TypeConverters(ListConverter::class) val exercises: List)
Considérations à noter:
– Quand une classe est annotée avec @Entity, le nom de la table sera le nom de la classe. Si nous voulons utiliser un nom différent, nous devons ajouter la propriété tableName avec l’annotation @Entity.
– L’annotation @TypeConverters doit être utilisée lorsque nous déclarons une propriété pour laquelle le type est une classe personnalisée, une liste, un type de date, ou tout autre type que Room et SQL ne savent pas comment sérialiser. Dans ce cas, nous utilisons l’annotation au niveau du champ de la classe et seul ce champ pourra l’utiliser. Selon l’endroit où l’annotation est placée, elle se comportera différemment comme expliqué ici.
Trainee.kt
Il représente le propriétaire de la routine.
@Entity(indices = , foreignKeys = , childColumns = )])data class Trainee( @PrimaryKey(autoGenerate = true) val id: Int, val name: String, val age: Int, val gender: Int?, @Embedded val routine: Routine)
DAOs
Les objets d’accès aux données (DAOs) sont utilisés pour accéder à nos données lorsque nous implémentons Room. Chaque DAO doit inclure un ensemble de méthodes pour manipuler les données (insérer, mettre à jour, supprimer ou obtenir).
Un DAO peut être implémenté comme une interface ou comme une classe abstraite. Dans notre cas, nous utilisons une interface. Comme tous les DAO sont fondamentalement identiques, nous n’en montrerons qu’un seul.
GenderDao.kt@Daointerface GenderDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertGender(gender: Gender) @Update fun updateGender(gender: Gender) @Delete fun deleteGender(gender: Gender) @Query("SELECT * FROM Gender WHERE name == :name") fun getGenderByName(name: String): List @Query("SELECT * FROM Gender") fun getGenders(): List}
Quelques choses à noter:
– Tous les DAO doivent être annotés avec @Dao.
– Une fonction annotée avec @Insert, @Update, ou @Delete doit recevoir une instance de la classe désirée comme paramètre, qui représente l’objet que nous voulons respectivement insérer, mettre à jour, ou supprimer.
– Dans le cas des opérations d’insertion ou de mise à jour, nous pouvons utiliser la propriété onConflict pour indiquer ce qu’il faut faire lorsqu’un conflit réalisant l’opération se produit. Les stratégies disponibles à utiliser sont : REPLACE, ABORT, FAIL, IGNORE et ROLLBACK.
– Si nous voulons obtenir des informations spécifiques d’une ou plusieurs entités, nous pouvons annoter une fonction avec @Query et fournir un script SQL comme paramètre.
Database
Représente la base de données. Elle détient une connexion à la base de données SQLite réelle.
AppDatabase.kt@Database(entities = , version = 1)@TypeConverters(DateTypeConverter::class)abstract class AppDatabase : RoomDatabase() { abstract fun exerciseDao(): ExerciseDao abstract fun genderDao(): GenderDao abstract fun routineDao(): RoutineDao abstract fun traineeDao(): TraineeDao companion object { var INSTANCE: AppDatabase? = null fun getAppDataBase(context: Context): AppDatabase? { if (INSTANCE == null){ synchronized(AppDatabase::class){ INSTANCE = Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "myDB").build() } } return INSTANCE } fun destroyDataBase(){ INSTANCE = null } }}
Ce qu’il faut remarquer ici :
– C’est une classe abstraite qui doit étendre RoomDatabase.
– Elle doit être annotée avec @Database, et elle reçoit une liste d’entités avec toutes les classes qui composent la base de données (toutes ces classes doivent être annotées avec @Entity). On doit aussi fournir une version de la base de données.
– Nous devons déclarer une fonction abstraite pour chacune des entités incluses dans l’annotation @Database. Cette fonction doit retourner le DAO correspondant (une classe annotée avec @Dao).
– Enfin, nous déclarons un objet compagnon pour obtenir un accès statique à la méthode getAppDataBase, qui nous donne une instance singleton de la base de données.
Convertisseurs de type
Les convertisseurs de type sont utilisés lorsque nous déclarons une propriété que Room et SQL ne savent pas comment sérialiser. Voyons un exemple de la façon de sérialiser un type de données ‘date’.
DateTypeConverter.ktclass DateTypeConverter { @TypeConverter fun fromTimestamp(value: Long?): Date? { return if (value == null) null else Date(value) } @TypeConverter fun dateToTimestamp(date: Date?): Long? { return date?.time }}
Utilisation de la base de données Room
Voyons maintenant un exemple très simple de la façon d’utiliser la base de données Room que nous venons de créer :
MainActivity.ktclass MainActivity : AppCompatActivity() { private var db: AppDatabase? = null private var genderDao: GenderDao? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) Observable.fromCallable({ db = AppDatabase.getAppDataBase(context = this) genderDao = db?.genderDao() var gender1 = Gender(name = "Male") var gender2 = Gender(name = "Female") with(genderDao){ this?.insertGender(gender1) this?.insertGender(gender2) } db?.genderDao()?.getGenders() }).doOnNext({ list -> var finalString = "" list?.map { finalString+= it.name+" - " } tv_message.text = finalString }).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe() }}
Que faisons-nous ?
– Récupérer l’instance de la base de données et GenderDao.
– Créer deux instances Gender : Male et Female.
– Insérer les deux instances créées dans la base de données.
– Interroger la base de données pour obtenir tous les genres qui y sont stockés.
– Fusionner le nom de tous les genres que nous avons obtenu de la base de données, et définir le texte du TextView avec cette valeur.
Room pour faire plus avec moins
Room est l’un des éléments importants des composants architecturaux Android. Il nous donne un cadre très robuste pour travailler avec et des informations persistantes, en assurant toujours la sécurité et l’intégrité des données. Il fournit également une facilité d’utilisation aux développeurs, afin qu’ils puissent écrire un code lisible et auto-explicatif. Si vous voulez faire plus avec moins de code, et aussi assurer la sécurité des données des utilisateurs, vous devriez utiliser Room comme couche de persistance sur votre app.
Et c’est tout ! C’est presque tout ce que vous devez savoir pour créer et utiliser une base de données sur Android avec Room. Obtenez le code complet de ce projet ici. Merci d’avoir lu ce tutoriel Android Room !