Contexte
Dart, et à fortiori Flutter, ne permet pas l’introspection, c’est à dire la possibilité d’analyser les objets manipulés pendant l’exécution du projet.
Cette fonctionnalité est largement utilisée en Java au travers notamment des annotations qui vont permettre simplement de modifier le comportement d’une objet du projet.
En Flutter, les annotations reposent sur la possibilité de générer du code, cette génération devant se faire en dehors de la phase de build des projets, typiquement lors du changement d’un objet à coté duquel du code doit être généré.
Cette génération de code est notamment utilisée par la librairie json_serializable qui permet d’annoter une classe de données et d’obtenir les méthodes fromJson et toJson sur cette dernière.
Nous nous sommes servis sur de nombreux use cases de cette fonctionnalité de génération de code, mais n’avions pas eu l’occasion de créer une librairie la mettant en pratique.
Les ateliers proposés par Mobiapps nous ont semblés être le moment rêvé pour tenter l’aventure.
Présentation du sujet
Notre but était d’avoir un exemple simple mais fonctionnel qui nous permettrait de tester cet outil.
Nous avons décidé de partir du template de projet fourni par Flutter permettant sur une page d’avoir un compteur qu’on incrémente par un clic sur un bouton.
Dans ce projet, l’idée était d’inclure un BLoC prenant en charge la gestion de l’état de la page, et de générer des méthodes permettant d’incrémenter ou de décrémenter une valeur.
Le code de base est le suivant :
Dans le widget
Dans le BLoC
L’idée est de pouvoir générer une méthode int increment(int value) qui effectuera le calcul et pourra être accessible dans le BLoC en la mettant à disposition dans une mixin.
Mise en œuvre de la librairie
La mise en place d’un module incluant de la génération de code n’a de sens qu’au sein d’un package Flutter.Nous avons donc créé un nouveau package incluant, dans le répertoire example du projet, le code du template incluant le compteur incrémentable.
Dans un deuxième temps, il faut pour un package de ce type ajouter deux dépendances au projet :
- source_gen (section dependencies) : framework mettant à disposition les outils permettant de construire les générateurs de code
- build_runner (section dev_dependencies) : outil en ligne de commande permettant d’exécuter la génération de code proprement dite
Trois éléments seront nécessaires dans le package pour permettre l’utilisation d’annotations dans le projet cible :
- Une classe définissant l’annotation ; pour une annotation “simple”, c’est à dire sans paramètres, il suffit de définir la classe et un constructeur.
- Une classe héritant de GeneratorForAnnotation ; la classe parent prend en type l’annotation créée et attend la redéfinition d’une méthode generateForAnnotatedElement qui fournit en sortie, en fonction de l’élément sur lequel l’annotation est posée, le code à générer.
- Une fonction renvoyant un objet de type Builder ; ici on va référencer la classe précédente en fournissant une instance de cette dernière aux outils de génération.
On ajoutera enfin, à la racine du projet, un fichier build.yaml permettant la configuration du build_runner (référencement des générateurs, définition de l’extension à utiliser sur les fichiers générés… cf https://github.com/dart-lang/source_gen/blob/master/example/build.yaml ).
Utilisation de la librairie
Une fois le code du générateur mis en place, il ne reste qu'à utiliser l’annotation dans notre projet d’exemple :
Enfin on pourra générer le code en lançant la commande flutter pub run build_runner build ce qui met à disposition le code suivant :
Template personnalisable
La redéfinition de la méthode generateForAnnotatedElement, nous permet de récupérer l’élément sur lequel l’annotation a été posée. À l’aide de ces éléments, on va construire notre template qui sera généré lors de l’exécution de la commande de build.
Ce template est représenté sous la forme d’une chaîne de caractère entièrement personnalisable. Nous avons eu l’occasion de parcourir les variables de l’élément récupéré et celui-ci est très complet. Dans l’exemple précèdent, nous avons utilisé uniquement le nom de la classe, cependant si des fonctions sont définies dans la classe héritant, chaque information de la fonction, les variables définies, leurs retours ou leurs types seront accessibles.
C’est un template qui est totalement personnalisable et qui peut totalement s’adapter au besoin, dans l’éventualité ou le template contiendrait une erreur d’implémentation, la commande de build, retourne les erreurs rencontrées.
Conclusion
La mise en place d’un générateur s’avère extrêmement simple en Flutter, nous avons pu mettre en place ce petit outil très rapidement. La construction du template généré n’est pas plus difficile, il est nécessaire de prendre le temps pour le développer et de correctement définir les variables de la classe héritant pour éviter un traitement supplémentaire lors de la génération.
Nous étudions à présent la possibilité d’utiliser cet outil pour générer, dans nos projets, une partie du code lié à notre implémentation de la clean architecture.
Vous souhaitez en savoir plus ? Contactez-nous !