Optimiser les appels réseaux sur votre Application Mobile
Dans cet article, nous allons voir comment optimiser les appels réseaux sur votre application mobile. Ici quand on parle “d’optimiser”, on parle surtout de réduire le nombre d’appels réseau et leur fréquence. Le but est d’éviter les appels inutiles quand cela est possible.
Ci-après quelques points qui montrent l'intérêt de réduire les appels réseaux sur une application mobile :
- Diminue la consommation de la batterie pour le téléphone.
- Monopolise moins de ressources matérielles du téléphone.
- Vos utilisateurs auront une application utilisable même avec une connexion internet fluctuante.
- En termes d’éco-conception, moins vous faites d'appels, moins vous polluez la planète.
- Si vous utilisez une solution d’hébergement pour votre serveur, moins d’appels signifie très souvent moins d’argent dépensé.
La question est donc comment faire pour minimiser le nombre d’appels réseaux effectués par votre application mobile ?
Est ce que la donnée est présente ?
La première vérification à faire avant de faire un appel réseau, c’est de regarder si la donnée que vous voulez récupérer n’est pas déjà présente dans votre application. En effet, lorsque que vous naviguez dans votre application, vous effectuez des appels réseaux pour récupérer des données. Il est possible que vous tentiez de récupérer de la données déjà récupérée précédemment et que vous voulez réutiliser. Notamment s'il s’agit de données plus ou moins statiques, qui ne changent pas souvent dans le temps (exemple : une liste d’articles vendus).
Cette donnée déjà présente peut être stockée dans la couche data de votre application ou bien directement dans le state de votre application.
Mais qu’est ce que le state ? La plupart des applications mobiles possèdent un état à un instant donné. Cet état est appelé plus communément state de l’application. Ce state contient les données métiers que vous voulez afficher à vos utilisateurs. Pour gérer le state, le framework de développement mobile hybride Flutter propose plusieurs solutions dites de state management. Ces librairies vous donnent des outils pour simplifier la gestion des événements qui auront un impact sur le state. Il existe un certain nombre de librairies de state management et chacune vient avec ses avantages et ses inconvénients. Je ne m’étendrai pas plus que ça sur comment choisir la bonne librairie de state management et je vous renvoie vers cet autre article qui détaille très bien ce sujet.
Revenons à notre problématique, comment minimiser le nombre d’appels réseaux effectués par votre application ?
Pour cela, une première possibilité est d’utiliser le state de notre application comme une sorte de cache. Pour donner un exemple concret, si vous avez une page qui affiche une liste d’articles. En arrivant sur la page, vous allez faire un appel réseau pour récupérer votre liste. Mais si vous l’utilisateur quitte cette page et reviens dessus, vous n’avez pas forcément envie de refaire cet appel car votre liste d'articles n'aura pas forcément évolué entre temps.
Et tout l’enjeu est là, il faut bien comprendre d'où vient la donnée qui est récupérée et comment elle évolue. Si vous êtes sur une application de messagerie instantanée, vous allez avoir des nouveaux messages qui vont arriver assez régulièrement donc la donnée va souvent évoluer donc on ne veut pas vraiment garder notre liste de messages en cache dans notre state.
Ci-dessous un exemple d’implémentation en Flutter qui utilise le framework de state management Redux (pour plus d’info sur Redux, je vous renvoi à cet article) :
class MonAction {
final bool forceReload;
const MonAction({this.forceReload = false});
}
Ici, le but est de préciser au framework si on veut forcer le rechargement des données ou non. Pour cela, on ajoute simplement un boolean forceReload porté par notre action (qui est propagé quand l’utilisateur arrive sur la page par exemple).
Future<void> _onMonActionMiddleware(Store store, MonAction action){
if(action.forceReload || store.state.monState is StateError){
repository.getData();
}
}
Puis dans notre middleware, on ne va effectuer l’appel réseau que sous plusieurs conditions. Est ce que le forceReload vaut true, si oui on effectue l’appel pour rafraîchir nos données. Est ce que notre state est en erreur, si oui, ça signifie qu’une erreur est potentiellement survenue lors du premier appel et que nous n’avons pas récupérer nos données donc on veut refaire également notre appel.
Avec ce mécanisme, par défaut si les données ont déjà été récupérées et qu’on ne force pas explicitement le refresh, on ne refera pas d’appel réseau inutile.
Tirer parti de la puissance de GraphQL
Il existe un autre mécanisme qui nous permet de minimiser le nombre d'appels réseau effectués dans une application mobile qui utilise le protocole réseau GraphQL. GraphQL est un langage de requête d’API qui peut servir d’alternative au protocole REST. Je ne vais encore une fois pas m’attarder plus que ça sur son fonctionnement, si vous voulez en savoir plus je vous renvoie vers cet autre article.
Je vais plutôt m’intéresser à une particularité du fonctionnement du GraphQL qui va nous permettre de minimiser de manière assez efficace le nombre d'appels réseau effectués par notre application mobile.
Nous allons de nouveau prendre un cas d’usage concret que vous pourrez rencontrer dans vos applications. Par exemple, vous avez un écran qui va afficher une liste d'articles que vous avez achetés et une liste d'articles que vous avez vendu. Comme cela arrive assez souvent, ces deux listes proviennent de deux sources réseau différentes (par exemple deux endpoint d’API différents ou dans le cas de GraphQL, deux query distincte). Voici à quoi ressemblerait donc le schéma GraphQL que l’on devrait utiliser :
type Query {
getArticlesAchetes(userId: String!): [ArticleModel]
getArticlesVendu(userId: String!): [ArticleModel]
}
type ArticleModel {
nom: String!
description: String!
prix: Float!
}
La plupart du temps, on va donc à l'arrivée sur la page, effectuer deux appels réseaux en appelant nos deux query, l’une pour récupérer la liste des articles achetés et l’autre pour récupérer celle des articles vendus. Et c’est là qu'intervient la particularité de GraphQL, vous avez la possibilité de grouper des query comme ci dessous :
query get_articles($userId: String!){
getArticlesAchetes(userId: $userId){
nom
description
prix
}
getArticlesVendu(userId: $userId){
nom
description
prix
}
}
Vous allez donc créer votre requête qui va récupérer les données des deux query de votre schéma, ce qui permet de ne pas faire deux appels réseau mais un seul.
Vous devinez peut-être que ce mécanisme ne se limite pas à deux query mais a autant que vous voulez. On peut donc maintenant imaginer que sur la page d’accueil d’une application assez complète, on devrait récupérer des données issus de 5 ou 6 query différente et donc on pourrait passer de 5 ou 6 appels réseau à un seul !
Le fait de grouper les requêtes pour ne faire qu’un appel réseau va nous permettre d’économiser de la batterie et des ressources matérielles de votre téléphone. En effet, votre téléphone utilise une puce radio pour communiquer avec l’extérieur par le réseau. Or cette puce n’est pas allumée en permanence mais uniquement lorsqu'on effectue un appel. Et lorsque cette puce est allumée, cela consomme la batterie du téléphone de manière significative. Ainsi, grouper nos requêtes en 1 appel permet de laisser la puce allumée le moins longtemps possible et donc économiser de la batterie.
GraphQL possède une autre spécificité qui nous permet de transporter un volume de données moins important. En effet, lorsqu’on écrit une requête GraphQL, on peut demander au serveur uniquement les informations qui sont nécessaires, ce qui permet d’éviter de récupérer des données inutiles dans certains cas.
Utiliser un mécanisme de cache HTTP
Lorsqu’on parle de réduire le trafic réseau dans une application, la plupart des gens qui se sont déjà intéressés au sujet sont tombés sur des solutions de cache HTTP. Le cache HTTP est une sorte de mémoire tampon, qui se situe entre votre serveur et votre application et qui permet de stocker des données fréquemment demandées pour les fournir de manière plus efficace à votre application sans refaire un appel jusqu’au serveur. Le cache HTTP est régi par des standards. Ce mécanisme de cache utilise l’en-tête HTTP Cache-Control qui contient des instructions pour configurer la mise en cache côté serveur et côté client. Pour plus d’informations, je vous renvoie vers cet article.
Ici, nous allons aborder de manière plus spécifique, le mécanisme de cache mis à disposition par les librairie réseau que vous allez utiliser dans votre application mobile qui se base sur le cache HTTP.
Par exemple, pour effectuer des appels réseau en utilisant GraphQL en Flutter, on utilise une librairie qui vous fournit des classes et notamment un client qui vous permet de simplifier votre code. Il existe plusieurs librairies tel que graphql, graphql_flutter, ferry etc. Certaines d'entre elles fournissent des outils qui vous permettent de mettre vos données en cache pour éviter de refaire un appel réseau.
Ici nous allons nous intéresser à l’une d’entre elles, Ferry. Cette librairie met à disposition un système de cache assez simple d’utilisation, vous permettant de mettre en cache les données récupérées de vos query GraphQL. Pour cela, il suffit de préciser la stratégie de cache lors de la création de votre query :
final query = Gget_articlesReq(
(builder) => builder
..fetchPolicy = FetchPolicy.CacheFirst
..userId = "userId",
);
Ici, on utilise la stratégie CacheFirst proposé par Ferry, c’est d’ailleurs la stratégie par défaut qui sera utilisée pour les query si rien n’est précisés. Cette stratégie est très simple, lors de l’appel, ferry va vérifier si dans le cache, on a déjà un résultat pour cette query avec ces paramètres. Si oui, on prend les données du cache sans faire l’appel réseau. Si non, on fait l’appel réseau et on remplit le cache en prévision d’un prochain appel identique.
2 spécificités importantes :
Le cache se base sur le nom de la query et ses paramètres d’entrée, si vous refaites la même query avec un identifiant différent, on refera un appel.
Le cache de Ferry est stocké dans la mémoire du téléphone et non directement dans l’application ce qui signifie que si l’application est fermée, le cache n’est pas perdu.
Ces deux spécificités sont à bien garder en tête car si elles permettent d’optimiser l’utilisation du cache, cela peut également provoquer des bugs assez importants.
Conclusion
Pour conclure, nous avons vu plusieurs manières d’optimiser le nombre d’appels réseaux dans une application mobile. En résumé, voici les 3 points à retenir :
Se poser la question : Est ce que la donnée est déjà présente ? Si oui, utilisez votre application comme un cache pour ré-utiliser les données récupérées côté serveur tant qu'elles ne sont pas considérées “out of date”.
Grâce à GraphQL, grouper les query dans 1 seul et même appels réseau pour récupérer toutes les données nécessaire d’un coup et récupérer uniquement les informations nécessaire pour transporter moins de paquet dans votre échange réseau.
Les libraires réseau (REST et GraphQL) fournissent des services de cache qui utilisent la mémoire du téléphone pour conserver les données et donc ne pas refaire un appel à votre serveur.
Enfin, je vous invite à rester vigilant quant au fait que conserver la donnée en cache ou dans votre application au lieu de la rafraîchir n’est pas une décision à prendre seule ou juste entre développeurs. Ces discussions concernent également le métier car il faut bien comprendre comment vie vos données et à quelle fréquence elles changent au risque de voir des “bug” apparaître.