Dans une démarche de test unitaires, l'un des enjeux principaux consiste à mocker les dépendances des fonctions/méthodes testées, c'est-à-dire à passer en paramètre des objets qui imitent le comportement des variables attendues, sans pour autant les implémenter réellement. Dans l'écosystème Flutter et Dart, la bibliothèque la plus populaire est mockito. Cette dernière propose une API très accessible pour mocker les classes et surcharger leur comportement en fonction du besoin de nos tests.
L'une des limitations de cette approche est qu'elle repose sur de la génération de code via le build_runner de Dart. Elle requiert de relancer la génération de code à chaque modification de nos classes, afin de mettre à jour l'ensemble des mocks. Bien qu’il soit très répandu dans l’écosystème Dart, Le principe de code génération est une approche qui reste assez controversée dans la communauté. En effet, elle ajoute une étape de friction dans les tâches de développement et introduit un code non "maîtrisé" dans la base de code.
Afin de pallier ce problème, Felix Angelov propose une solution alternative : Mocktail. Mocktail est basée sur une API très similaire à celle de Mockito. Cependant l'implémentation est différente car elle n'utilise pas la génération de code. Celà permet de mocker toutes les dépendances de nos tests, en bénéficiant de la nullsafety. L'expérience de développement devient ainsi bien plus fluide.
Cette approche a rapidement conquis à l'unanimité les experts Flutter de chez BAM pour devenir le standard de mock sur tous nos projets.
Toute application cliente s'interface avec un ou plusieurs serveurs, qui permettent de récupérer les données qu'elle affiche, et d’y envoyer les informations que l'utilisateur renseigne. Avec Flutter, la définition de clients API qui permet ces interactions peut être fastidieuse et nécessiter une grande quantité de code boilerplate. C’est notamment le cas pour créer une (dé)sérialisation JSON/dart des données échangées.
La solution OpenAPI Generator permet de générer automatiquement l'ensemble du code dart responsable de l'interfaçage, avec une API conforme au standard Open API 3.
Cela implique de générer un fichier .yaml de contrat d'API depuis le code back, afin de l'utiliser pour générer le client. Il est aussi possible de créer ce fichier manuellement si l'API respecte la norme.
Cette approche présente de nombreux bénéfices :
Après avoir employé cette approche sur plusieurs projets en production, nous avons cependant identifié 2 freins majeurs à la mise en place de cette génération :
Si ces 2 freins sont bien adressés, on peut réduire la complexité de l'écriture d'un client API dart complet, en un unique fichier .yaml.
Lors du développement d'un projet Flutter, qu'il s'agisse d'une application ou bien d'un package, nous pouvons rapidement être amenés à nous interfacer avec du code natif mobile : iOS ou Android. Pour permettre aux développeurs de répondre à ce besoin, Flutter propose le système de PlatformChannel : des canaux de communication entre le code natif de la plateforme et le code dart de Flutter. Cette approche, bien documentée par Flutter, présente 2 limitations:
Pour pallier ces limitations, Flutter propose le package Pigeon. Cette solution de génération de code permet de créer automatiquement l'ensemble des briques constitutives d'un PlatformChannel, en assurant le typage des données transférées. Cette approche permet donc de gagner un temps considérable sur la génération du code, nécessaire au fonctionnement du PlatformChannel. En parallèle, elle vient garantir le typage des données échangées de part et d'autre, prévenant ainsi de nombreux bugs.
On note cependant quelques limitations telles que l'absence de support des events channels ou de la gestion des enums. La librairie n’est pas encore compatible avec des plateformes desktop.
En dépit de ces quelques limitations, nous recommandons fortement l’usage de Pigeon. Notre recommandation s'appuie notamment sur son appartenance aux packages officiels Flutter ainsi que sur son dynamisme, comme en témoigne le grand nombre d'évolutions sur les derniers mois.
Comme tout framework réactif, Flutter repose sur l'existence d'un State dont la mise à jour déclenche une modification de l'UI. Depuis sa création, les solutions de State Management dédiées à Flutter se multiplient.
BLoC est la solution développée par Félix Angelov pour le State Management des applications Flutter. Créée fin 2018, elle apparaît aujourd'hui, comme beaucoup d'autres, dans la liste des solutions de State Management recommandées pour Flutter, mais elle s’est également installée de manière pérenne dans la communauté avec : plus de 400 releases, une 8e version sortie cette année et des sponsors très impliqués dans la communauté Flutter (ex : Very Good Ventures). Nous avons eu l'occasion de tester BLoC sur plusieurs projets en production chez BAM.
BLoC présente une approche structurée et prédictible avec des objets aux responsabilités clairement définies (ex : Event, State et Bloc) et elle se combine très bien avec le package Freezed pour limiter l'écriture manuelle de code boilerplate. Par ailleurs, elle propose une très bonne intégration dans les éditeurs de code avec des snippets permettant d’instancier les widgets un clin d'œil.
Sa documentation très complète et alimentée par de nombreux exemples, ainsi que sa communauté active, présentent deux de ses atouts majeurs. Enfin, l'aisance avec laquelle il est possible de tester et de mocker le code écrit en utilisant BloC a permis à nos équipes d'atteindre et de maintenir une couverture de test de 100% sur nos derniers projets.
Bien que nous soyons convaincus par cette solution et que nous la recommandons pour gérer le State d'une application Flutter, nous maintenons son statut en Trial car d'autres solutions telle que Riverpod (également présente dans ce Tech Radar) sont également recommandables. A ce jour, nous n’avons pas identifié de grand vainqueur sur le terrain du State Management associé à Flutter.
Au même titre que BLoC et Provider, Riverpod est une solution de State Management dédiée à Flutter. Elle apparaît en 2ème position dans la documentation “List of state management” de Flutter, juste derrière son prédécesseur Provider et avant les solutions "Native" de Flutter (ex: setState et InheritedWidget). Cet excellent positionnement est un premier indicateur de la qualité et de la stabilité de la solution.
Riverpod a été créée pour répondre à 2 limitations de Provider :
Par ailleurs, elle présente de nombreuses qualités telles que sa syntaxe minimaliste et sa capacité à être facilement mockable et testable.
Enfin, Riverpod se rapproche aujourd’hui de la solution homologue react : react-query en proposant une excellente gestion de l'asynchrone avec des états de loading, error, data et refreshing présents out of the box, qui permettent d’afficher l'UI correspondant à chaque état de chargement d'une donnée.
Si cette solution nous a convaincus après l'avoir utilisée sur plusieurs projets en production, et que nous la recommandons pour gérer le state d'une application Flutter, nous avons décidé de maintenir son statut en trial. En effet, d'autres solutions telles que BLoC (présente dans ce Tech Radar) sont également recommandables. A date, nous n’avons pas identifié de grand vainqueur sur le terrain du "State Management" associé à Flutter.
En Flutter, les tests d'éléments visuels (widgets, pages) s'appuient sur le système de golden tests, qui consiste à générer un rendu du widget testé au format image.
Lorsque les tests sont lancés à nouveau, la nouvelle image est comparée à la version précédente. Si une différence de pixels est détectée, le test échoue et met en garde contre les régressions. Il est possible de configurer la tolérance d'un pourcentage d'erreurs, cependant, dans la réalité, la donnée varie fortement en fonction du design et de l'écran ciblé. Il est donc difficile de déterminer un seuil de tolérance pertinent et commun à tous les types de designs.
Dans la vie d'un projet, les suites de tests sont lancées par des développeurs sur leurs machines locales, qui opèrent sous Mac OS (contrainte des développeurs mobiles), mais également sur les machines d'intégration continue, que nous configurons pour opérer sous Linux (les machines d'intégration continue sous MacOS sont ~ 5 à 10 fois plus onéreuses).
Cette différence d'OS entre les machines de développement et les machines d'intégration continue peut paraître anodine, mais en cas de golden tests sur des éléments textuels, elle devient problématique.
En effet, les golden tests affichant du texte apparaissent différents sur les plateformes MacOS et Linux, causant des faux négatifs dans les tests lancés en CI.
La solution Alchemist propose un ensemble d'outils permettant de faciliter l'utilisation des golden tests en Flutter. L'une de ses fonctionnalités phares vise à résoudre le problème évoqué ci-dessus, en générant des images goldens différentes, pour les tests en local et ceux de la CI.
Par ailleurs, Alchemist permet de générer plusieurs goldens au sein d'une même page, pour plus de concision.
Enfin, en attribuant un label golden aux tests goldens, elle permet de lancer indépendamment et parallèlement les tests goldens et les tests classiques afin d'optimiser la vitesse d'exécution de la suite de tests.
Nous avons testé cette librairie chez BAM et les résultats étaient encourageants. Néanmoins son adoption reste encore marginale avec moins de 200 stars sur Github et une popularité < 80% sur pub.dev. Nous vous recommandons donc de surveiller ce projet pour voir s'il devient plus adopté dans la communauté.
Certaines architectures projets se basent sur la notion de multi-packages. En d’autres termes, un seul répertoire va contenir plusieurs packages, correspondant chacun à une brique indépendamment développable, testable et déployable.
Ces packages sont souvent interdépendants et il devient difficile pour le package manager (quelle que soit la techno: npm, pub, composer, pip, gem ...) de résoudre les dépendances de chaque package. Et ce, à la fois dans un environnement de production et dans un environnement local de développement.
Les dépendances liées à un environnement de production doivent être résolues depuis le site du provider (pub.dev, npmjs.com ...). Côté développement, le point de départ est le code source local de chaque package.
En Flutter, et plus généralement en Dart, ce problème est résolu par Melos, qui propose un outil en ligne de commande pour ces "mono-repos". Il résout les dépendances avec un linking local, permet le lancement de commandes sur l'ensemble des packages (analyze, test...) et propose automatiquement le versioning et le déploiement de chaque package pour s’intégrer facilement sur une CI.
Après avoir utilisé Melos plusieurs mois sur un projet de SDK Flutter, nous avons rencontré des difficultés pour débogger certaines erreurs de CI causées par cet outil, et perdu du temps parfois précieux.
Par ailleurs, la documentation indique que le package est en développement actif et non pas stabilisé, bien qu'utilisé par des grands noms tels que FlutterFire.
Si nous vous invitons à garder un œil sur Melos qui, porté par l'entreprise Invertase (fer de lance de l'open source Flutter), promet de devenir la référence de son domaine, nous le positionnons aujourd'hui dans la catégorie "Assess". Au regard des éléments évoqués, cette solution n’a pas atteint une maturité suffisante pour être adoptée les yeux fermés.
La capacité à récupérer les données exposées par un serveur, via une API, constitue une fonctionnalité clé pour toute application mobile.
Depuis quelques années, le standard GraphQL s'impose progressivement comme une alternative à REST, au sein de la communauté. Aujourd'hui, les applications développées en Flutter peuvent également échanger leurs données avec une API GraphQL, notamment grâce à 3 solutions disponibles : graphql_flutter, ferry et artemis.
Parmi ces 3 solutions, graphql_flutter est la plus populaire (avec 3k stars github à ce jour). Cette approche, très inspirée d'Apollo (référence des clients GraphQL de l'écosystème React) est très complète et présente de nombreux atouts.
Parmi eux, on note la gestion du hors ligne avec l'intégration d'un cache, d'un système de polling/rediffusion et le support des résultats optimistes.
On observe cependant quelques limitations relatives au support des fichiers .graphql et à la génération de code, dont la documentation reste légère et qui nécessite l'utilisation des hooks (un pattern qui n’est pas arrivé à maturité en Flutter).
La solution ferry, plus récente, propose une autre approche en insistant sur une intégration solide de la génération de code, qui permet un typage fort des objets manipulés. Utilisée par nos équipes sur l'un de nos derniers projets et aujourd'hui en production, cette solution a donné lieu à un retour d'expérience mitigé. Pour cause, un goulot d'étranglement au niveau de la maintenance (absence de réponses sur plusieurs issues créées par nos équipes et 85% des commits réalisés par le créateur). N’ayant pas encore eu l'occasion de tester la solution Artemis sur un projet, il nous semble prématuré de vous proposer une évaluation quelconque à ce stade. Par ailleurs, nos expériences récentes avec les différents clients GraphQL de l'écosystème Flutter ne nous permettent pas de faire ressortir nettement une solution solide à recommander. Nous restons pour l’instant sceptiques sur la maturité de cette approche.
Au même titre que BLoC, Provider est l’une des solutions de State Management les plus populaires.
Provider a été conçu très tôt, par Rémi Rousselet, comme un wrapper autour de la solution native de Flutter: InheritedWidget, pour le rendre plus simple d'utilisation et plus scalable avec une API Provider/Consumer simple à appréhender. Cette solution constitue aujourd'hui l'approche standard pour le State Management et l'injection de dépendances des apps Flutter, comme en témoigne le label Flutter Favorite qui lui est attribué.
Si Provider apparaît toujours comme une approche stable et solide pour gérer le State d'une application Flutter, cette solution présente certaines limites telles que le chargement asynchrone des providers, le support de plusieurs providers de même type ou encore les exceptions au runtime. L’émergence de solutions plus modernes telles que BLoC ou encore de Riverpod, également publiée par l’auteur de Provider, et construite essentiellement pour résoudre les défauts de Provider, en font aujourd'hui une solution de second plan. En d’autres termes, si vous souhaitez utiliser Provider, notre discours pourrait être simplifié en une phrase : “Utilisez Riverpod, c'est pareil, mais en mieux"
Retrouvez l'avis de nos experts sur les techniques, plateformes, outils, langages et frameworks associés aux principales technologies mobiles que nous utilisons au quotidien chez BAM : React Native, Flutter et Native.