Android Architecture Components - Mise en pratique (Part 5 - Fuel)

Ceci est la 5è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 4-View

La solution de cette partie est disponible sur le commit 5-Fuel

Introduction

Fuel est une librairie Kotlin qui facilite la réalisation d'appels HTTP. Elle supporte tout type de requête (GET, PUT, POST etc). Nous allons surtout la préférer à ses concurrentes (pensons à Retrofit) puisqu'elle a un plugin pour les coroutines.

Fuel nous servira à récupérer de la data sur l'API de coinmarket : https://coinmarketcap.com/api/.

Configuration des endpoints

Oubliez le fichier Constants.java / Constants.kt. Gradle permet nativement de créer des configurations par environnement.

Le build par défaut (en environnement de développement) est debug.

Dans app/build.gradle, ajouter l'adresse du endpoint utilisé dans le buildType "debug" :

Note: Rappelons que ces constantes ne sont accessibles que dans un build de debug (celui exécuté par défaut)

Pour que l'application soit la plus simple possible, nous interrogerons seulement un endpoint : ticker. La query param "structure=array" indique que la data soit contenue dans un tableau JSON.

Ajout des dépendances

Toujours dans app/build.gradle, ajouter les dépendances suivantes :

Fuel

La dernière dépendance embarque gson. C'est une librairie Java plébiscitée pour sérialiser / désérialiser du JSON.

Coroutines

Modification de l'AndroidManifest

N'oublions pas d'ajouter la permission INTERNET à notre manifest pour nous permettre de réaliser des appels HTTP :

Création du routing

FuelRouting, comme son nom le laisse l'entendre, sert au routing avec Fuel.

Créer une classe CoinRouting dans un nouveau package api :

On définit une seule route : un appel GET sur l'endpoint ticker/, avec en query param structure=array.

Création du mapping JSON / Kotlin

Créer une classe CoinResult contenant un tableau de coins :

Adaptation au format de retour de la réponse

Chaque currency a une structure complexe imbriquée : "quotes" :

Pour garder un objet Coin simple, nous allons donc créer notre propre deserializer. Nous nous limiterons aussi à la récupération de la valeur des cryptos en USD.

Création des désérialiseurs

Créer tout d'abord un désérialiseur gson CoinTypeAdapter dans le package api :

Créer ensuite un désérialiseur Fuel CoinsResultDeserializer dans le même package. Il utilisera le désérialiseur Gson:

Mise à disposition pour injection

Nous utilisons notre fraîche configuration de Dagger pour rendre Gson et CoinResultDeserializer injectables. Modifier di/AppModule :

Points à noter :

  • Les valeurs null sont sérialisées
  • Les champs _transient _et static sont ignorés.
  • Les champs pris en compte par gson doivent avoir l'annotation @Expose

Robustesse

Pour gérer les statuts de chargement de nos données, nous allons créer :

  • un wrapper de données
  • une énumération des états d'un appel

Création de l'énumération

Créer dans un nouveau package util une énumération Status. Pour le moment, elle possède trois états :

  • SUCCESS
  • LOADING
  • ERROR

Création du wrapper

Créer le wrapper Resource dans le même package :

L'intérêt de cette encapsulation est double :

  • Notification à l'utilisateur de l'état de chargement des données
  • Gestion des cas d'erreurs HTTP

Gestion du rafraîchissement

Pour que l'utilisateur puisse rafraîchir les valeurs des cryptomonnaies, nous allons intégrer un mécanisme de rafraîchissement.

Dans activity_main.xml :

SwipeRefreshLayout encapsule nativement la gestion du swipe (du haut vers le bas) pour afficher une animation de chargement. Nous avons maintenant tout le nécessaire pour faire des appels sur l'API de CoinMarket.

Création du workflow d'appel

Reprenons MainActivity.

Nous allons tout d'abord déclarer notre deserializer. Il sera injecté :

Puisque nous remplirons la liste de Coins avec les données de coinmarket, elle sera désormais vide à son initialisation :

Il faut aussi indiquer à Dagger que nous utilisons l'injection dans notre activité. Dans la fonction onCreate, ajouter :

Nous allons utiliser le pattern Producer / Consumer pour gérer l'afflux de données. Cela permet de :

  • réaliser des traitements non bloquants
  • afficher en temps réel l'état des traitements

Nous utiliserons les coroutines pour l'implémenter :

GlobalScope.produce(Dispatchers.Default, 0) {...} crée une coroutine. Le type de Dispatchers spécifie son contexte d'exécution. Default correspond à un pool de threads partagé sur la JVM.

Voici les étapes de la récupération de la data. C'est notre producer :

  • Envoi d'un tableau vide avec un statut "LOADING".
  • Exécution de la requête avec Fuel :
    • Utilisation du router CoinsRouting.GetCoins()
    • AwaitObject : attend en asynchrone la réponse du serveur et indique le désérialiseur utilisé en cas de succès. C'est ici qu'on utilise le coinsResultDeserializer.
    • Fold : il indique ce que l'on fait de la réponse. En cas de succès, on wrappe l'objet (SUCCESS) désérialisé et on l'envoie. En cas d'erreur, on envoie un tableau vide wrappé en Erreur.

Voici le consumer correspondant :

GlobalScope.launch(Dispatchers.Main, CoroutineStart.DEFAULT), {...} crée la coroutine. Nous utilisons le

A chaque send de notre producer, consumeEach est déclenché. Selon le statut de ce qui est reçu :

  • Error : le message d'erreur est affiché sous forme de toast. Le loader est stoppé.
  • Loading: on affiche le loader
  • Success: on update les valeurs des coins et on cache le loader.

Voici le contenu de la méthode updateCoins :

Ici :

  • on vide la liste
  • on la remplit avec les nouveaux éléments
  • on notifie l'adapter que les données ont changé.

Dans l'état actuel des choses, tout est prêt pour récupérer et afficher nos données. Pour déclencher l'action, nous avons besoin d'un listener :

Nous l'affectons à chaque retour sur l'activité et le supprimons lorsqu'elle est mise en pause.

Bonus : pour modifier la couleur du loader, ajouter dans la fonction init :

Conclusion

Désormais, votre application peut afficher dynamiquement les valeurs des cryptocurrencies ! Elle est aussi robuste aux problèmes réseaux. En revanche, elle ne fonctionne pas en mode hors-ligne. De plus, elle est sensible au changement d'orientation puisque la liste de monnaies est vidée à chaque fois. La prochaine étape résoudra ce problème.

Sources