Travailler avec des collections mutables est toujours risqué en raison de leurs possibles effets secondaires. Dart propose UnmodifiableListView comme solution intégrée, mais elle n'est pas 100% fiable car elle peut échouer lors de l’exécution. À la place, nous avons choisi d'utiliser le package fast_immutable_collections, qui offre une liste immuable sécurisée lors de la compilation appelée IList (ainsi que ISet pour les ensembles et IMap pour les cartes).
Nous utilisons actuellement IList pour toutes nos apps, car il s’agit d’un remplacement simple de List. De plus, il présente peu de dépendances et prend en charge une large gamme de versions de Dart, ce qui nous permet de l'utiliser également dans nos plugins sans risque de conflits de dépendances.
Non seulement IList est moins sujet aux erreurs, mais il offre également une syntaxe plus propre lorsqu'il est utilisé avec d'autres états immuables (comme les classes de freezed) en évitant le besoin de copie défensive. De plus, IList inclut des méthodes pratiques telles que removeDuplicates et intersectsWith. Le seul inconvénient que nous avons trouvé est que la syntaxe pour créer une liste constante (const IListConst([])) est légèrement laborieuse.
NOTRE POINT DE VUE
Avec très peu d’inconvénients et un impact positif important sur la qualité et la maintenabilité, nous avons intégré fast_immutable_collections à tous nos projets Dart (à la fois les applications et les plugins) et nous vous recommandons vivement de faire de même.
HISTORIQUE 2022
Il s'agit d'un nouveau blip cette année.
Afin de réduire la complexité d'un projet de SDK, il est possible de le découper en plusieurs packages indépendants. Ces packages sont développés et versionnés ensemble, on parle alors souvent de "mono-repos". Cette architecture permet de séparer le développement, les tests et le déploiement de chaque brique en processus indépendants, mais cette modularité ajoute une problématique majeure : comment gérer les interdépendances entre packages, à la fois en développement local et une fois publiés ?
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, scripts customs...) et propose automatiquement le versioning et la publication de chaque package sur pub.dev, ce qui en fait un outil parfait pour s'intégrer facilement sur une CI/CD.
L'an dernier, nous adoptions une posture réservée sur Melos. Depuis, la documentation s'est grandement améliorée,de nombreux bugs ont été résolus et la version 3 a amélioré la syntaxe utilisée pour la configuration de Melos. Pour le debugging, un système de logging vous permet facilement de savoir dans quel package vos scripts ont rencontré un problème.
Melos étant développé par Invertase, un acteur solide de l'écosystème open source Flutter et Dart, nous avons confiance dans le fait que la librairie sera continuellement améliorée et mise à jour pour répondre aux besoins de la communauté. Les développeurs d'Invertase utilisent d'ailleurs Melos pour développer leurs propres outils, comme FlutterFire.
NOTRE POINT DE VUE
La facilité de développement garantie par Melos et l'absence d'alternative mature, nous ont décidés à adopter cette librairie pour nos projets de SDK Dart et Flutter. Nous recommandons Melos à tous les développeurs de packages, en particulier ceux qui travaillent sur des projets complexes avec plusieurs packages et équipes de développement.
HISTORIQUE 2022
Assess - Voir le Tech Radar 2022
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. 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 deux problématiques :
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 quelques limitations comme l'absence de support des events channels et l'incompatibilité avec des plateformes desktop. Des améliorations sont également possibles sur la syntaxe un peu lourde pour la gestion des enums ou le support des collections d'objets non nuls.
Ces limitations sont également présentes sans utilisation de pigeon et nous n'observons pas d'alternative plus efficace pour répondre à ce besoin. Par ailleurs, certaines évolutions à venir, comme l'utilisation de ffigen permettant d'appeler les langages natifs directement depuis le code dart, promettent d'offrir une solution encore plus performante et complète.
NOTRE POINT DE VUE
Après le développement de deux packages et près d'un an et demi d'utilisation, nous recommandons fortement Pigeon.
HISTORIQUE 2022
Adopt - Voir le Tech Radar 2022
En Flutter, la gestion du state global est réalisée grâce aux InheritedWidget et ChangeNotifier, mais ces APIs sont très verbeuses et bas niveau. C'est pour simplifier leur utilisation que Provider a été créé, et naturellement, la communauté Flutter a largement adopté cette solution. Plus récemment, le créateur de Provider a introduit un nouveau package de state management : Riverpod.
Riverpod a été créé pour répondre à 3 limitations de Provider :
Par ailleurs, Riverpod présente de nombreuses qualités telles que sa syntaxe minimaliste et sa capacité à être facilement mockable et testable. Riverpod évolue constamment avec le support de nouvelles fonctionnalités tel que le support du state asynchrone, des outils de refactoring intégrés, des règles de lint adaptées.
Enfin l'auteur du package a annoncé de belles évolutions pour le futur, notamment la persistance du state et le support du static meta programming.
NOTRE POINT DE VUE
Après l'avoir utilisée sur plusieurs projets en production, Riverpod est devenue la solution de state management que nous choisissons par défaut pour nos projets d'application mobile chez BAM.
HISTORIQUE 2022
Trial - Voir le Tech Radar 2022
Le langage Dart mise beaucoup sur son excellente expérience de développement (DevX), assurée par sa syntaxe familière et ses puissantes intégrations aux IDE les plus connus comme VSCode ou Android Studio. Avec plus de 220 règles de lints créées par les teams Dart et Flutter, le linter est une des fonctionnalités les plus remarquables de ces intégrations. Cependant, il arrive que toutes ces règles ne suffisent pas pour mettre en application certaines conventions d'équipe particulières.
Dans cette situation, il est possible de recourir à la librairie custom_lint.
Grâce à une API relativement simple d'utilisation, cette librairie rend possible l'écriture de règles personnalisées pour l'analyzer Dart. Ces règles peuvent ensuite être ajoutées à l'analyzer dans le fichier analysis_options.yaml, de la même manière que les règles classiques. Comme chaque règle créée avec custom_lint est un package Dart, il est possible de les publier sur pub.dev et de les partager ainsi avec l'ensemble de la communauté.
Créée très récemment, custom_lint ne peut pas être adoptée les yeux fermés. Plusieurs limitations restent gênantes dans l'intégration de la librairie à certains projets. Elle est particulièrement difficile, voire impossible, pour les mono-repos qui contiennent plusieurs packages Dart ou Flutter ne partageant pas les mêmes dépendances.
Le package étant activement en développement, ses dépendances sont très souvent mises à jour et imposent des contraintes parfois difficiles à suivre, sur les projets aux nombreuses dépendances qui ont souvent un cycle de mise à jour plus long. Pour tirer le plein potentiel de custom_lint, il est nécessaire d'avoir une connaissance intermédiaire de la librairie analyzer. Cette dernière est de très bas niveau et présente un coût d'entrée important.
NOTRE POINT DE VUE
Après l'avoir utilisée pendant plusieurs mois sur un projet d'application Flutter, et sur un projet de SDK, nous pensons que custom_lint est une librairie prometteuse, qui devrait rapidement devenir une référence incontournable dans la communauté. En raison de l’absence d'alternative fiable et des limitations mentionnées précédemment, nous avons décidé de placer custom_lint en “trial”. Nous avons l’intention de suivre de très près son évolution dans les mois à venir.
HISTORIQUE 2022
Il s'agit d'un nouveau blip cette année.
La première API de navigation en Flutter, appelée Navigator, est relativement simple. Pour naviguer vers une page, il faut la pousser sur la pile de navigation. Pour revenir en arrière, il faut la retirer de la pile. Malgré sa simplicité et sa popularité, le Navigator comporte des lacunes et des limitations :
Avec le support du web, l'équipe Flutter a introduit une nouvelle API de navigation : Router. Contrairement à son prédécesseur qui fonctionne de manière impérative, le Router est une API déclarative.
Cependant, cette API est d’un niveau extrêmement bas. Pour s'en servir, l'utilisation d'une librairie est nécessaire.
Plusieurs options sont disponibles, comme RouteMaster et Beamer, labellisées Flutter Favorite. Sur nos projets, nous utilisons GoRouter. Initialement indépendant, GoRouter est depuis maintenu par l'équipe interne de Flutter. Cette librairie supporte la navigation par URL, les deeplinks sans configuration supplémentaire et permet d'écrire facilement des tests de navigation. Il existe même une extension, basée sur la génération de code, qui permet de typer les différentes routes.
Notre expérience avec GoRouter est globalement positive, bien que nous relevons des problèmes lors de l'utilisation de navigateurs imbriqués.
NOTRE POINT DE VUE
Nous vous recommandons d'utiliser GoRouter mais avec une certaine précaution tout en gardant un œil sur les alternatives sérieuses disponibles, d'où un positionnement en "trial".
HISTORIQUE 2022
Il s'agit d'un nouveau blip cette année.
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 avec le package graphql_flutter (avec 3.1k stars github à ce jour).
Le package graphql_flutter fournit une intégration transparente avec GraphQL, permettant aux développeurs de définir des requêtes et des mutations en utilisant des chaînes de caractères GraphQL. De plus, il offre la possibilité de faire du cache, du polling/rediffusion et du support des résultats optimistes, qui sont des fonctionnalités très utiles pour améliorer les performances et l'expérience utilisateur de l'application.
Le package graphql_flutter est également compatible avec le package graphql_codegen, qui permet la génération de code pour le typage fort des objets manipulés et le support des fragments. Cette fonctionnalité est particulièrement utile pour les grands projets qui nécessitent un code robuste ainsi que facilement maintenable et réutilisable.
Bien que graphql_flutter pousse à faire les calls apis directement dans vos widgets ou via des hooks Flutter, il est tout à fait possible de l'utiliser avec d'autres solutions de state management sans trop de difficultés. Cependant, l'utilisation des streams du client GraphQL dans ces solutions de state management peut se révéler complexe.
NOTRE POINT DE VUE
Notre expérience de l'utilisation de GraphQL en Flutter s'est nettement améliorée depuis l'année dernière et nous sommes désormais plus confiants pour l'utiliser sur des projets.
HISTORIQUE 2022
Hold - Voir le Tech Radar 2022
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 Dart, 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 le code dart responsable de l'interfaçage, avec une API conforme au standard OpenAPI 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 éprouvé cette approche sur plusieurs projets en production, nous avons constaté un frein majeur à la mise en place de cette génération : certaines spécifications OpenAPI ne sont pas supportées nativement par le langage Dart, spécifiquement les objets qui peuvent être de plusieurs types (oneOf et allOf). Un client dart généré via le générateur OpenAPI ne supportera donc pas ces types. Il est alors nécessaire de définir les modèles API avec une librairie basée sur la code generation comme Freezed.
NOTRE POINT DE VUE
Même si nous utilisons un générateur OpenAPI sur chacun de nos projets en production, cette limitation importante, à laquelle nous avons été confrontés à plusieurs reprises sur nos projets, nous pousse à rétrograder cette solution en “trial”.
HISTORIQUE 2022
Adopt - Voir le Tech Radar 2022
Flutter utilise Skia comme bibliothèque de rendu cross plateforme pour dessiner les interfaces utilisateur. Skia est une bibliothèque performante, optimisée pour les processeurs graphiques modernes, offrant à Flutter des performances élevées sur les plateformes mobiles. C’est également le moteur de rendu graphique de chrome.
En utilisant des shaders, des programmes permettant de manipuler les graphismes et les effets visuels, Skia peut produire des effets sophistiqués et des transitions fluides au sein de l’interface utilisateur.
Néanmoins, le processus de compilation des shaders, effectué au runtime, peut entraîner des problèmes de jank (ralentissements et saccades) lors de l’exécution d’une application Flutter, notamment sur iOS.
Pour remédier à ces problèmes, une solution courante consiste à pré-compiler les shaders au build time. Cette approche est actuellement limitée à iOS.
Sur Android, cette méthode n’est pas compatible avec tous les modèles de smartphone qui utilisent différents pilotes graphiques. Cette étape de pré-compilation est chronophage car elle nécessite de parcourir l’ensemble de l’application sur un appareil physique pour “enregistrer” les shaders pré-compilés, ce qui la rend difficilement scalable.
C’est pourquoi Flutter a introduit Impeller, un nouveau moteur graphique, conçu pour remplacer Skia et résoudre les problèmes de jank. Impeller est capable de compiler un ensemble plus petit et plus simple de shaders. Il peut également s’appuyer sur les APIs graphiques natives comme Vulcan sur Android et Metal sur iOS, pour tirer parti de la puissance du processeur graphique du téléphone.
Récemment devenu disponible sur iOS, son utilisation permet d’obtenir des performances graphique bluffantes. Il est désormais activé par défaut sur les apps iOS depuis la version 3.10 de Flutter. Sur nos applications mobiles, nous avons observé une nette amélioration de la fluidité des animations. Nous avons cependant constaté quelques bugs visuels, qui sont néanmoins rapidement fixés par l’équipe Flutter.
NOTRE POINT DE VUE
Impeller apparait clairement comme le futur moteur graphique de Flutter. Nous vous recommandons de l’activer sur vos projets, tout en restant vigilants aux régressions visuelles qu’il pourrait introduire.
HISTORIQUE 2022
Il s'agit d'un nouveau blip cette année.
En développement mobile, les cycles d'itérations sont bien plus longs qu'en web :
chaque nouvelle version d'application mobile doit être validée par les stores (Apple et Google) rallongeant ainsi le temps entre le moment où un correctif urgent est implémenté et le moment où il est disponible pour les utilisateurs ;
il faut attendre que chaque utilisateur décide de mettre à jour l'application pour bénéficier du correctif ;
dans les environnements de staging et QA, qui peuvent être déployés plusieurs fois par jour, les temps de build natif mobile et le fait de devoir télécharger les nouvelles versions de l'application ajoutent de la friction dans les processus de déploiement.
Pour répondre à ce problème et déployer des changements mineurs sans passer par le store, les applications développées en React Native bénéficient depuis plusieurs années maintenant de solution du type: "over-the-air" update CodePush, Expo Updates qui permettent d'envoyer des mises à jour de l'application développées en javascript, sans passer par les stores. Cette solution est très appréciée par les développeurs et les utilisateurs, mais elle n'est pas disponible pour les applications développées en Flutter.
Début 2023, le créateur de Flutter, Éric Seidel a annoncé la création d'un projet open source nommé Shorebird, qui permettra de déployer des mises à jour des applications Flutter sans passer par les stores. Pour développer cette solution, il a été rapidement rejoint par Felix Angelov, dont le nom est familier aux développeurs Flutter puisqu'il est l'auteur des packages BLoC et Mason.
À l'écriture de ce blip, la solution Shorebird est toujours en phase early stage de développement et ne fonctionne que pour Android. Il est donc trop tôt pour se prononcer sur son adoption.
NOTRE POINT DE VUE
Notre expérience de CodePush nous donne envie de bénéficier d'une solution similaire pour nos applications Flutter. De plus, la renommée des deux développeurs impliqués dans le projet nous rassure quant à la qualité de la solution qui sera proposée.
HISTORIQUE 2022
Il s'agit d'un nouveau blip cette année.
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.