Android Architecture Components - Mise en pratique (Part 7 - Room)

Ceci est la 7ème partie d'un lot de 7 tutoriels.

Vous pouvez repartir de l'étape précédente ou tirer la solution contenue dans le commit 6-ViewModel_LiveData

La solution de cette partie est disponible sur le commit 7-Room.

Pourquoi Room?

Ouvrez votre navigateur. Allez sur votre moteur de recherche préféré. Tapez "android sql library". Notez le nombre, impressionnant, de résultats. Certaines librairies sont dépréciées. D'autres peu utilisées. D'autres encore sont justes limitées.

C'est dans ce contexte qu'intervient Room. Via ce wrapper SQLite, Google propose une solution qui est :

  • Simple. Elle se base sur des annotations.
  • (J'ose espérer) Durable. Google est à son initiative.
  • (Relativement) Riche. Elle offre des mécanismes de migration, est bien documentée, force à découper son code…

Realm et Android Couchbase (article sur le sujet ici) sont des alternatives populaires. En revanche, puisqu'elles ajoutent leur propre mécanisme de gestion de données, elles allourdiront l'APK. Utiliser Room, c'est aussi prendre l'outil officiel poussé par Google.

Ajout des dépendances

Définition de la table (entity) Coin

Une Entity dans Room est l'équivalent d'une table en SQL.

Modifier la classe Coin :

Nous laissons SQLite générer automatiquement la clé primaire. On peut aussi :

  • Spécifier le nom de chaque champ en base
  • Définir des contraintes (que ce soit Foreign Key ou Unique)
  • Inclure d'autres entités
  • etc

Création du DAO

Créer dans le package db une interface CoinDao :

Ce DAO contient deux fonctions :

  1. La première, pour insérer un tableau de Coin. En cas de conflit (ie de clé primaire déjà existante), le tuple est remplacé.
  2. La deuxième, pour récupérer la liste des monnaies. Elles sont ordonnées par rang.

Remarque ; En retournant un LiveData, on crée une Observable Query. Concrètement, cela signifie que :

  • Si la table COIN est modifiée (opération CRUD), la requête est automatiquement rejouée
  • Le callback du LiveData est déclenché avec les données mises à jour (updateCoins dans MainActivity)

Ce fonctionnement est déconcertant de prime abord, puisqu'il paraît "magique". Dans les faits, les observable queries assurent d'avoir des données à jour.

Définition de la database

Créer dans db une classe abstraite AppDatabase :

Notre seule entité est Coin. Nous n'exportons pas le schéma dans un fichier SQL. Notre seul Dao est CoinDao.

Ajouts dans Dagger

Nous allons rendre notre Database et notre Dao injectables. Modifier AppModule :

Mise en place du pattern Repository

Présentation

Dans un contexte mobile, on est amenés à récupérer des données auprès de différentes sources : base de données, webservices, etc. L'application peut alors rapidement devenir fouillie et complexe : dépendances, partage de responsabilités, duplication...

repository_pattern

Source

Un repository, c'est une brique chargée de centraliser l'ensemble des datasources. En le mettant en place :

  • La couche data est isolée
  • Les rôles des briques applicatives sont bien définis
  • L'ajout de nouvelles datasources est facilité

Implémentation

Renommer le package util en repository. Y créer une classe CoinRepository :

Dans ce fichier, il y a beaucoup de points à noter. Tout d'abord :

  • C'est un singleton
  • Il est injectable
  • coinDao et coinResultDeserializer sont résolus via injection de dépendances

_coins est de type MediatorLiveData. Cette sous-classe de LiveData peut observer plusieurs sources de données en même temps. C'est donc parfait pour un Repository. On lui ajoute sa première datasource dans le bloc init : la base de données. La valeur de _coins est automatiquement mise à jour après un changement en base.

Nous rendons visible _coins (avec underscore) à travers la variable coins (sans l'underscore). Ainsi, le tableau de monnaies est en lecture seule.

Pourquoi? coins est de type LiveData. LiveData n'autorise pas la modification de la valeur qu'il contient mais seulement sa lecture. Principe d'encapsulation de l'objet.

Dans ce contexte, la fonction getCoins() diffère par rapport à celle en partie 6. Nous n'avons plus besoin d'envoyer la data récupérée de coinmarket. La sauvegarder en base suffit.

Pourquoi? Rappelons-nous que _coins se met à jour à chaque changement en base.

En cas d'erreur / pendant le chargement, nous renvoyons la valeur courante de _coins.

La fonction fetchCoins() est exactement la même qu'en partie 6.

Modification de CoinViewModel

Reprendre CoinViewModel et le modifier :

Nous lions simplement coins contenu dans ce ViewModel à celui contenu dans le Repository. coinRepository est injecté.

Voici au final à quoi ressemble l'application :

7-Room

Conclusion

Ceci conclut ce guide pratique de développement avec les AAC. J'y ai intégré les composants qui me semblent essentiels. J'ai choisi de vous présenter androidx et les coroutines bien qu'elles soient en phase active de développement. En effet, ce sont des librairies qui seront clés dans les prochains mois. En revanche, leurs implémentations seront amenées à changer. Au final, les outils présentés aident à respecter des principes généraux de développement :

  • casser les dépendances
  • structurer son code
  • bien choisir (et réfléchir) au kit de développement utilisé

Sources