Introduction
Flutter est aujourd’hui un Framework très populaire pour le développement d’applications mobiles performantes.
Au fil des développements, les applications proposent de plus en plus de fonctionnalité et gagnent donc en complexité. C’est alors que la gestion des états au sein de l’application devient cruciale, car elle permet d’assurer la cohérence et l’intégrité des données pour l’utilisateur.
Sans gestion d'état, il devient difficile pour le développeur de gérer des interactions complexes dans une application.
Aujourd’hui, de nombreuses librairies de state management sont proposées pour les développeurs d’applications Flutter. Chacune d’entre elles permettent de gérer l'état de manière efficace mais chacune a ses propres forces, faiblesses et cas d’utilisation.
Dans cet article, nous nous pencherons sur l’utilisation de Riverpod, l’une des librairies de state management les plus utilisées et appréciées des développeurs Flutter. L’objectif est ici de fournir une vision globale sur cette solution.
Nous l’illustrerons sur un POC d’application simple permettant d’afficher une liste de films, une fiche de détails de film, en utilisant l’API TMDB.
Riverpod
La librairie Riverpod a été créée par Rémi Rousselet et a vu sa première version apparaitre en août 2020.
Avec sa récente version majeure 2.0, elle est devenue plus complète, plus stable et plus simple à utiliser.
Anagramme de Provider, Riverpod s’est revendiqué comme une amélioration de son prédécesseur et suit le “provider pattern” qui revendique l’idée de centraliser à un endroit unique l'état de l’application et éviter ainsi qu’il soit dispersé dans l’application. Cela simplifie globalement le raisonnement et organise plus proprement le code du développeur.
Riverpod utilise des fournisseurs, appelés Provider. Ce sont eux qui permettent de gérer l'état de l’application. Ils peuvent être imbriqués les uns dans les autres pour représenter des relations complexes entre différents éléments de l'état. La librairie a la capacité de gérer de manière évolutive et efficace les dépendances entre les providers.
Intégration de la librairie
Riverpod est séparé en plusieurs librairies, en fonction de l’usage que l’on veut en faire. Pour l’utiliser dans notre projet Flutter, il a fallu ajouter le package flutter_riverpod dans le fichier pubspec.yaml.
Il est possible d’utiliser Riverpod avec des hooks, qui facilitent la déclaration et l’utilisation de certains provider, dans ce cas il faut ajouter les librairies flutter_hooks et hooks_riverpod. Pour commencer, nous avons choisi d’utiliser Riverpod sans les hooks afin de bien en prendre en main et comprendre l’outil.
Exposer un provider
Comme nous l'évoquions, les providers sont la partie centrale dans l’utilisation de Riverpod. Ils permettent d’encapsuler et d’exposer un état de l’application.
Les providers remplacent ainsi à eux seuls plusieurs patterns, tels que les singletons, l'injection de dépendances ou encore les « inheritedwidgets ».
Ils vont aussi permettre de simplifier la combinaison des états avec une syntaxe simple, et d’optimiser l’application en limitant la reconstruction des widgets.
Les providers doivent être déclarés avec cet objet en paramètre :
Pour faire fonctionner et rendre disponible les providers dans notre architecture de widgets dans l’applications, il est nécessaire d’ajouter le widget ProviderScope au niveau le plus haut de notre application :
Il existe plusieurs types de providers, nous allons parler de ceux que l’on a utilisé. L’ intégralité des providers mis à disposition par Riverpod sont listés ici.
FutureProvider
Un FutureProvider est un type de provider proposé par Riverpod qui vous permet de gérer des valeurs asynchrones dans votre application Flutter. Contrairement à d'autres types de providers, tel que le Provider standard qui n'accepte que des valeurs synchrones, FutureProvider renvoie une instance de type Future.
Dans notre POC, nous l'avons utilisé afin de récupérer de manière asynchrones la liste des films.
StreamProvider
Un StreamProvider permet d'écouter un flux d'information qui a besoin d'être mis à jour en continu.
Dans notre POC, nous avons mis en place un décompte pour indiquer la sortie du film en utilisant un stream sur la date/heure de sortie de ce dernier. Puis nous l'avons combiné avec un ref du côté de notre widget pour récupérer le stream.
Accéder à un provider
Pour pouvoir accéder à un provider ou interagir avec, il est avant tout nécessaire d’obtenir l’objet “ref”.
Ce dernier va ensuite pouvoir être utilisé pour interagir avec le provider :
- ref.watch pour reconstruire le widget qui s’est abonné au provider lorsque sa valeur change
- ref.listen pour exécuter une opération spécifique dès que la valeur du provider évolue (contrairement à ref.watch, ne reconstruit pas le widget)
- ref.read pour obtenir la valeur du provider à un instant t en ignorant les changements à venir ou pour appeler une méthode du provider. A utiliser le moins possible selon la documentation pour des soucis de performances. Peut être nécessaire dans les cas suivants par exemple : lecture de la valeur du provider au clic sur un bouton, dans le initState d'un widget.
Plusieurs façons sont proposées par Riverpod pour accéder à l'objet ref. En voici quelques-unes :
Depuis un widget
- · StatelessWidget: en héritant du ConsumerWidget au lieu de StatelessWidget
Le ConsumerWidget est similaire à un StatelessWidget. La seule différence est que sa méthode build possédera un paramètre supplémentaire : l'objet ref.
- StatefulWidget: en héritant de ConsumerStatefulWidget eu lieu de StatefulWidget+ ConsumerState au lieu de State
De la même manière, l’objet ref est mis à disposition, cette fois en tant que propriété de la classe ConsumerState.
- · En utilisant le widget Consumer
Depuis un autre provider
Retour d’expérience
Habitués à utiliser la librairie “concurrente” flutter_bloc, permettant également de faire du state management, nous avons pu au cours de cet après-midi de pratique, comparer la mise en œuvre et les principes de Riverpod vs. Flutter BLoC.
Modularité et testabilité
Essayant de respecter au mieux la Clean Architecture, nous nous efforçons de séparer les responsabilités des couches logicielles. Que nous utilisions BLoC ou Riverpod, la gestion des états de l’application est bien séparée de la présentation, l’UI. Cette séparation permet d’avoir une base de code plus évolutive, maintenable et testable.
Favorisant tous deux une architecture modulaire, BLoC et Riverpod promeuvent la facilité d'écriture de tests automatisés, permettant de tester la logique de l’application de manière isolée, sans avoir à se préoccuper de l’UI. L’objectif étant toujours de réduire le risque de bogues et d’améliorer la qualité globale du code.
Avec la librairie BLoC, le state management est assuré par une architecture pilotée par les événements, où le widget s'abonne à ces changements de mises à jour de l'état et se reconstruit ainsi chaque fois que l'état change.
Avec Riverpod, le state management est réalisé par Provider, une solution de state management directement intégrée à Flutter. Les providers gèrent l'état et l'exposent à leurs enfants dans l'arbre des widgets via le contexte.
Injection de dépendances
BLoC et Riverpod permettent tous deux de transmettre de l'état de l’application à l’arbre de widgets comme s’il s’agissait d’une injection de dépendances.
Avec BLoC, les blocs peuvent être exposés et sont rendus accessibles par l’intermédiaire de BlocProvider.
Avec Riverpod, l'état est exposé de la même manière en utilisant les providers.
Simplicité
Une utilisation complète de la librairie BLoC demandera au développeur d'écrire les événements, les états qui en découlent et le BLoC pour l’implémentation de la logique intermédiaire.
Riverpod quant à lui ne gère pas d'événements mais uniquement des états. Pour l'équipe de développeurs, cela peut rendre le code plus simple, moins verbeux mais aussi moins structuré qu’en utilisant BLoC selon l’utilisation qu’on en fait. Là encore, c’est une histoire de goûts !
Riverpod propose directement dans sa solution une base de 3 états (fail, loaded et loading) via la classe AsyncValue, ce qui peut s’avérer pratique à l’utilisation.
Dans sa dernière version, Riverpod propose de la génération de code pour simplifier encore plus le travail du développeur en exploitant les annotations proposées.
Synthèse
Finalement, Riverpod et BLoC sont deux solutions de state management populaires pour des applications Flutter, ayant chacune d’entre elles des forces et des faiblesses. Il n’y a pas une solution meilleure que l’autre. Choisir l’une ou l’autre des solutions doit se faire selon les besoins, les contraintes spécifiques du projet et les appétences des développeurs.
Les deux librairies ont prouvé leur efficacité et ont à présent une communauté d’utilisateurs et de développeurs relativement riche et grandissante, avec des mises à jour régulières permettant de répondre aux évolutions de l'écosystème Flutter.
L'échelle et la complexité du projet doivent bien entendu être un critère de choix. Quelque soit la solution de gestion des états retenue, il est primordial de comprendre les concepts sous-jacents de state management pour assurer un développement d’applications performantes, évolutives et maintenables, avec une bonne expérience utilisateur.
NOTE
Dans le cadre du POC, nous avons utilisé les librairies suivantes :
- dio et retrofit pour les requêtes à l’API TMDB
- go_router pour la navigation in-app
- freezed pour la génération des modèles de données
Luc ALLARDIN – Développeur Mobile
Anthony AUMOND – Développeur Mobile
Fabien DHERMY – Développeur Mobile
Majid SERHANE – Développeur Mobile