1. Avant de commencer
Introduction
Dans cet atelier de programmation, vous allez améliorer l'expérience utilisateur pour une application en utilisant la mise en cache hors connexion. De nombreuses applications s'appuient sur des données provenant du réseau. Si votre application récupère les données du serveur à chaque lancement, et présente un écran de chargement pendant ce temps, cela peut nuire à l'expérience et conduire les utilisateurs à désinstaller votre application.
Lorsqu'ils lancent une application, les utilisateurs veulent voir les données rapidement. Vous pouvez répondre à cette attente en implémentant la mise en cache hors connexion. La mise en cache hors connexion permet à votre application d'enregistrer les données récupérées sur le réseau dans l'espace de stockage local de l'appareil, ce qui accélère l'accès.
L'application pourra récupérer les données du réseau tout en conservant un cache hors connexion des résultats précédemment téléchargés. Par conséquent, vous aurez besoin d'un moyen d'organiser ces différentes sources de données pour votre application. Pour ce faire, vous allez implémenter une classe de dépôt qui servira de référence unique pour les données de l'application et extraire la source des données (réseau, cache, etc.) du modèle de vue.
Ce que vous devez déjà savoir
Vous devez maîtriser les éléments suivants :
- Room, la bibliothèque de persistance pour vos données
- Retrofit, la bibliothèque réseau
- Les composants d'architecture Android de base,
ViewModel
,ViewModelFactory
etLiveData
- Les transformations pour une classe LiveData
- Créer et lancer une coroutine
- Les adaptateurs pour la liaison de données
Points abordés
- Comment implémenter un dépôt pour séparer la couche de données du reste de l'application
- Comment charger des données mises en cache depuis un dépôt
Objectifs de l'atelier
- Extraire la couche de données à l'aide d'un dépôt et intégrer la classe de dépôt avec la
ViewModel
- Afficher les données du cache hors connexion
2. Code de démarrage
Télécharger le code du projet
Notez que le nom du dossier est RepositoryPattern-Starter
. Sélectionnez ce dossier lorsque vous ouvrez le projet dans Android Studio.
Pour obtenir le code de cet atelier de programmation et l'ouvrir dans Android Studio, procédez comme suit :
Obtenir le code
- Cliquez sur l'URL indiquée. La page GitHub du projet s'ouvre dans un navigateur.
- Vérifiez que le nom de la branche correspond au nom spécifié dans l'atelier de programmation. Par exemple, dans la capture d'écran suivante, le nom de la branche est main.
- Sur la page GitHub du projet, cliquez sur le bouton Code pour afficher une fenêtre pop-up.
- Dans la fenêtre pop-up, cliquez sur le bouton Download ZIP (Télécharger le fichier ZIP) pour enregistrer le projet sur votre ordinateur. Attendez la fin du téléchargement.
- Recherchez le fichier sur votre ordinateur (il se trouve probablement dans le dossier Téléchargements).
- Double-cliquez sur le fichier ZIP pour le décompresser. Un dossier contenant les fichiers du projet est alors créé.
Ouvrir le projet dans Android Studio
- Lancez Android Studio.
- Dans la fenêtre Welcome to Android Studio (Bienvenue dans Android Studio), cliquez sur Open (Ouvrir).
Remarque : Si Android Studio est déjà ouvert, sélectionnez l'option de menu File > Open (Fichier > Ouvrir).
- Dans l'explorateur de fichiers, accédez à l'emplacement du dossier du projet décompressé (il se trouve probablement dans le dossier Téléchargements).
- Double-cliquez sur le dossier de ce projet.
- Attendez qu'Android Studio ouvre le projet.
- Cliquez sur le bouton Run (Exécuter) pour compiler et exécuter l'appli. Assurez-vous que tout fonctionne comme prévu.
3. Présentation de l'application de démarrage
L'application DevBytes présente une liste de vidéos DevBytes provenant de la chaîne YouTube des développeurs Android dans un RecyclerView, où les utilisateurs peuvent cliquer sur les liens vers les vidéos.
Le code de démarrage fonctionne correctement, mais il présente un défaut majeur qui peut nuire à l'expérience utilisateur. Si la connexion Internet de l'utilisateur est instable ou indisponible, aucune vidéo ne s'affiche. Cela se produit même si l'application a déjà été ouverte. Si l'utilisateur quitte et redémarre l'application, cette fois sans connexion Internet, l'application essaie (et échoue) de télécharger à nouveau la liste des vidéos.
Vous pouvez observer ce comportement dans l'émulateur.
- Activez temporairement le mode Avion dans Android Emulator via Settings App > Network & Internet > Airplane mode (Application Paramètres > Réseau et Internet > Mode Avion).
- Exécutez l'application DevBytes et observez que l'écran reste vide.
- Pensez à désactiver le mode Avion avant de poursuivre l'atelier de programmation.
En effet, une fois que l'application DevBytes a téléchargé les données pour la première fois, rien n'est mis en cache pour une utilisation ultérieure. L'application comprend actuellement une base de données Room. Votre tâche consiste à l'utiliser pour implémenter une fonctionnalité de mise en cache et mettre à jour le modèle de vue afin d'utiliser un dépôt, qui téléchargera de nouvelles données ou les récupérera à partir de la base de données Room. La classe du dépôt extrait cette logique du modèle de vue, ce qui permet d'organiser et de dissocier votre code.
Le projet de démarrage est organisé en plusieurs packages.
Vous êtes invité à vous familiariser avec le code. Cependant, vous ne modifierez que deux fichiers : repository/VideosRepository.kt et viewmodels/DevByteViewModel. Tout d'abord, vous allez créer une classe VideosRepository
qui implémente le modèle de dépôt pour la mise en cache (vous en apprendrez plus à ce sujet dans les pages suivantes), puis vous mettrez à jour le DevByteViewModel
pour utiliser votre nouvelle VideosRepository
.
Avant de vous plonger dans le code, prenons quelques instants pour en savoir un peu plus sur la mise en cache et le schéma de dépôt.
4. Mise en cache et schéma de dépôt
Dépôts
Le schéma de dépôt est un schéma de conception (plus connu sous le nom anglais de design pattern) qui isole la couche de données du reste de l'application. Le terme "couche de données" fait référence à la partie de votre application, distincte de l'interface utilisateur (UI), qui traite les données et la logique métier de l'application en exposant des API de façon cohérente pour que le reste de l'application puisse accéder à ces données. L'UI présente les informations à l'utilisateur, mais c'est la couche de données qui inclut des éléments tels que le code de mise en réseau, les bases de données Room, le traitement des erreurs et tout élément de code qui lit ou manipule des données.
Un dépôt peut résoudre les conflits entre différentes sources de données (modèles persistants, services Web, caches, etc.) et centraliser les modifications apportées à ces données. Le diagramme ci-dessous montre comment les composants d'une application, tels que les activités, peuvent interagir avec les sources de données via un dépôt.
Pour implémenter un dépôt, vous devez utiliser une classe distincte, telle que VideosRepository
, que vous créerez dans la tâche suivante. La classe du dépôt isole les sources de données et fournit au reste de l'application une API propre pour accéder aux données. L'utilisation d'une classe de dépôt garantit que ce code est distinct de la classe ViewModel
, conformément aux recommandations de bonnes pratiques pour la séparation du code et l'architecture.
Avantages de l'utilisation d'un dépôt
Un module de dépôt gère les opérations de données et vous permet d'utiliser plusieurs backends. Dans les applications courantes, le dépôt implémente la logique permettant de décider s'il faut récupérer les données d'un réseau ou utiliser les résultats mis en cache dans une base de données locale. Un dépôt permet de permuter les détails de l'implémentation (par exemple, la migration vers une autre bibliothèque de persistance), sans affecter le code appelant, comme les modèles de vue. Cela permet également de rendre votre code modulaire et testable. Vous pouvez facilement simuler le dépôt et tester le reste du code.
Un dépôt peut servir de référence unique pour une partie spécifique des données de votre application. Lorsque vous travaillez avec plusieurs sources de données, telles qu'une ressource en réseau et un cache hors connexion, le dépôt garantit que les données de l'application sont aussi justes et à jour que possible, afin d'offrir la meilleure expérience même lorsque l'application est hors connexion.
Mise en cache
La mise en cache consiste à stocker les données utilisées par votre application. Par exemple, vous pouvez enregistrer momentanément les données du réseau, au cas où la connexion Internet de l'utilisateur serait interrompue. Même si le réseau n'est plus disponible, l'application peut toujours utiliser les données mises en cache. Un cache peut également être utile pour stocker des données temporaires d'une activité qui n'est plus affichée à l'écran, voire pour conserver des données persistantes entre deux lancements d'applications.
Les caches peuvent prendre différentes formes, simples ou complexes, en fonction de la tâche spécifique. Le tableau suivant présente plusieurs façons d'implémenter la mise en cache du réseau sous Android.
Technique de mise en cache | Exemples d'utilisation |
Retrofit est une bibliothèque réseau qui permet d'implémenter un client REST avec sûreté du typage pour Android. Vous pouvez configurer Retrofit pour stocker localement une copie de chaque résultat réseau. | Cette solution est adaptée aux requêtes et réponses simples, aux appels réseau occasionnels ou aux petits ensembles de données. |
Vous pouvez utiliser | Cette solution est adaptée à un petit nombre de clés et à des valeurs simples, telles que les paramètres de l'application. Vous ne pouvez pas utiliser cette technique pour stocker de grandes quantités de données structurées. |
Vous pouvez accéder au répertoire de stockage interne de l'application et y enregistrer les fichiers de données. Le nom du package de votre application indique le répertoire de stockage interne de l'application, qui se trouve à un emplacement spécial du système de fichiers Android. Ce répertoire est réservé à votre application et est effacé lorsqu'elle est désinstallée. | Cette solution est adaptée si vous avez des besoins spécifiques auxquels un système de fichiers peut répondre (par exemple, si vous devez enregistrer des fichiers multimédias ou des fichiers de données que vous devez gérer vous-même). Vous ne pouvez pas utiliser cette technique pour stocker des données complexes et structurées que votre application doit interroger. |
Vous pouvez mettre en cache des données à l'aide de Room, une bibliothèque de mappage d'objets SQLite qui fournit une couche d'abstraction sur SQLite. | Cette solution est recommandée pour les données structurées complexes pouvant être interrogées. Le meilleur moyen de stocker des données structurées sur le système de fichiers d'un appareil consiste à utiliser une base de données SQLite locale. |
Dans cet atelier de programmation, vous utiliserez Room, la méthode recommandée pour stocker des données structurées dans le système de fichiers d'un appareil. L'application DevBytes est déjà configurée afin d'utiliser Room. Votre tâche consiste à implémenter la mise en cache hors connexion à l'aide du schéma de dépôt, pour séparer la couche de données du code de l'interface utilisateur.
5. Implémenter VideosRepository
Tâche : créer un dépôt
Dans cette tâche, vous allez créer un dépôt pour gérer le cache hors connexion implémenté dans la tâche précédente. Votre base de données Room ne dispose pas d'une logique pour gérer le cache hors connexion. Elle dispose seulement de méthodes pour insérer, mettre à jour, supprimer ou récupérer les données. Le dépôt utilisera la logique pour récupérer les résultats du réseau et maintenir la base de données à jour.
Étape 1 : Ajouter un dépôt
- Dans repository/VideosRepository.kt, créez une classe
VideosRepository
. Transmettez un objetVideosDatabase
en tant que paramètre de constructeur de la classe pour accéder aux méthodes DAO.
class VideosRepository(private val database: VideosDatabase) {
}
- Dans la classe
VideosRepository
, ajoutez une méthodesuspend
appeléerefreshVideos()
, qui n'a aucun argument et ne renvoie rien. Cette méthode servira d'API pour actualiser le cache hors connexion.
suspend fun refreshVideos() {
}
- Dans la méthode
refreshVideos()
, basculez le contexte de coroutine surDispatchers.IO
pour effectuer des opérations réseau et de base de données.
suspend fun refreshVideos() {
withContext(Dispatchers.IO) {
}
}
- Dans le bloc
withContext
, récupérez la playlist de vidéosDevByte
à partir du réseau à l'aide de l'instance de service RetrofitDevByteNetwork
.
val playlist = DevByteNetwork.devbytes.getPlaylist()
- Dans la méthode
refreshVideos()
, après avoir récupéré la playlist sur le réseau, stockez-la dans la base de données Room. Pour stocker la playlist, utilisez la classeVideosDatabase
. Appelez la méthode DAOinsertAll()
en transmettant la playlist récupérée sur le réseau. Utilisez la fonction d'extensionasDatabaseModel()
pour mapper la playlist à l'objet de base de données.
database.videoDao.insertAll(playlist.asDatabaseModel())
- Voici la méthode
refreshVideos()
complète avec une instruction de journalisation pour son suivi lorsqu'elle est appelée :
suspend fun refreshVideos() {
withContext(Dispatchers.IO) {
val playlist = DevByteNetwork.devbytes.getPlaylist()
database.videoDao.insertAll(playlist.asDatabaseModel())
}
}
Étape 2 : Récupérer les données de la base de données
Dans cette étape, vous allez créer un objet LiveData
pour lire la playlist de vidéos depuis la base de données. Cet objet LiveData
sera mis à jour automatiquement à chaque fois que la base de données est modifiée. Le fragment ou l'activité associés sont actualisés avec les nouvelles valeurs.
- Dans la classe
VideosRepository
, déclarez un objetLiveData
appelévideos
pour contenir une liste d'objetsDevByteVideo
. Initialisez l'objetvideos
à l'aide dedatabase.videoDao
. Appelez la méthode DAOgetVideos()
. Comme la méthodegetVideos()
renvoie une liste d'objets de base de données, et non une liste d'objetsDevByteVideo
, Android Studio génère une erreur "type mismatch" (incohérence au niveau du type).
val videos: LiveData<List<DevByteVideo>> = database.videoDao.getVideos()
- Pour corriger l'erreur, utilisez
Transformations.map
pour convertir la liste des objets de base de données en une liste d'objets de domaine à l'aide de la fonction de conversionasDomainModel()
.
val videos: LiveData<List<DevByteVideo>> = Transformations.map(database.videoDao.getVideos()) {
it.asDomainModel()
}
Vous venez de mettre en œuvre un dépôt pour votre application. Dans la tâche suivante, vous utiliserez une stratégie d'actualisation simple pour maintenir la base de données locale à jour.
6. Utiliser VideosRepository dans DevByteViewModel
Tâche : intégrer le dépôt à l'aide d'une stratégie d'actualisation
Dans cette tâche, vous allez intégrer votre dépôt avec le ViewModel
en vous servant d'une stratégie d'actualisation simple. Vous afficherez la playlist de vidéos issue de la base de données Room, sans la récupérer directement sur le réseau.
Le processus d'actualisation d'une base de données consiste à mettre à jour ou à actualiser la base de données locale pour qu'elle reste synchronisée avec les données du réseau. Dans cette application exemple, vous utiliserez une stratégie d'actualisation simple : le module qui demande des données au dépôt est chargé d'actualiser les données locales.
Une application réelle pourra nécessiter une stratégie plus complexe. Par exemple, votre code peut actualiser automatiquement les données en arrière-plan (en tenant compte de la bande passante) ou mettre en cache les données que l'utilisateur est le plus susceptible d'utiliser ensuite.
- Accédez à viewmodels/DevByteViewModel.kt. Dans la classe
DevByteViewModel
, créez une variable de membre privée de typeVideosRepository
. Nommez-lavideosRepository
. Instanciez la variable en transmettant l'objet SingletonVideosDatabase
.
private val videosRepository = VideosRepository(getDatabase(application))
- Dans la classe
DevByteViewModel
, remplacez la méthoderefreshDataFromNetwork()
par la méthoderefreshDataFromRepository()
. L'ancienne méthoderefreshDataFromNetwork()
permettait de récupérer la playlist de vidéos à partir du réseau, à l'aide de la bibliothèque Retrofit. La nouvelle méthode charge la playlist de vidéos depuis le dépôt. Le dépôt détermine la source (réseau, base de données, etc.) à partir de laquelle la playlist est récupérée, ce qui maintient la séparation entre les détails de la mise en œuvre et le modèle de vue. Le dépôt facilite également la maintenance de votre code. À l'avenir, si vous modifiez l'implémentation servant à obtenir les données, vous n'aurez pas besoin de modifier le modèle de vue.
private fun refreshDataFromRepository() {
viewModelScope.launch {
try {
videosRepository.refreshVideos()
_eventNetworkError.value = false
_isNetworkErrorShown.value = false
} catch (networkError: IOException) {
// Show a Toast error message and hide the progress bar.
if(playlist.value.isNullOrEmpty())
_eventNetworkError.value = true
}
}
}
- Accédez à la classe
DevByteViewModel
. Dans le blocinit
, remplacez l'appel de fonctionrefreshDataFromNetwork()
parrefreshDataFromRepository()
. Ce code extrait la playlist de vidéos du dépôt, plutôt que directement du réseau.
init {
refreshDataFromRepository()
}
- Dans la classe
DevByteViewModel
, supprimez la propriété_playlist
et sa propriété secondaire,playlist
.
Code à supprimer :
private val _playlist = MutableLiveData<List<Video>>()
...
val playlist: LiveData<List<Video>>
get() = _playlist
- Dans la classe
DevByteViewModel
, après avoir instancié l'objetvideosRepository
, ajoutez une nouvelleval
appeléeplaylist
pour contenir une listeLiveData
des vidéos du dépôt.
val playlist = videosRepository.videos
- Exécutez votre application. L'application fonctionne comme auparavant, mais la playlist
DevBytes
est désormais extraite du réseau et enregistrée dans la base de données Room. La playlist qui s'affiche à l'écran provient de la base de données Room, et non directement du réseau.
- Pour voir la différence, activez le mode Avion sur l'émulateur ou l'appareil.
- Exécutez à nouveau l'application. Notez que le message toast d'erreur réseau ne s'affiche pas. La playlist est récupérée du cache hors connexion et affichée.
- Désactivez le mode Avion dans l'émulateur ou l'appareil.
- Fermez, puis rouvrez l'application. L'application charge la playlist depuis le cache hors connexion, tandis que la requête réseau s'exécute en arrière-plan.
Si de nouvelles données sont issues du réseau, l'écran est automatiquement mis à jour pour afficher les nouvelles données. Cependant, comme le serveur DevBytes
n'actualise pas son contenu, vous ne verrez aucune mise à jour des données.
Beau travail ! Dans cet atelier de programmation, vous avez intégré un cache hors connexion avec un ViewModel
afin d'afficher la playlist à partir du dépôt au lieu de la récupérer depuis le réseau.
7. Code de solution
Code de solution
Projet Android Studio : RepositoryPattern
8. Félicitations
Félicitations ! Voici un récapitulatif des acquis de ce parcours :
- La mise en cache est un processus qui consiste à stocker les données extraites d'un réseau dans l'espace de stockage d'un appareil. La mise en cache permet à votre application d'accéder aux données lorsque l'appareil est hors connexion ou si votre application doit de nouveau accéder aux mêmes données.
- Le meilleur moyen de stocker des données structurées dans le système de fichiers d'un appareil consiste à utiliser une base de données SQLite locale. Room est une bibliothèque de mappage d'objets SQLite, ce qui signifie qu'elle fournit une couche d'abstraction sur SQLite. Il est recommandé d'utiliser Room pour implémenter la mise en cache hors connexion.
- Une classe de dépôt isole les sources de données, telles qu'une base de données Room et des services Web, du reste de l'application. La classe du dépôt fournit une API propre permettant d'accéder aux données du reste de l'application.
- L'utilisation de dépôts est une pratique recommandée pour séparer le code de l'architecture.
- Lorsque vous mettez en œuvre un cache hors connexion, il est recommandé de séparer les objets réseau, domaine et base de données de l'application. Cette stratégie est un exemple de séparation des préoccupations.