Développement d’application en Flutter : retours d’expérience (1/2)

Au cours du développement de l’application PeerTube, nous avons acquis certaines expériences dans le choix des technologies employées et les freins que certaines décisions ont entraîné. Nous les partageons ici.

Pourquoi Flutter ?

Le développement d’applications mobiles pose rapidement la question du choix des technologies : faut-il créer une application distincte pour chaque plateforme, ou adopter une approche permettant de mutualiser les efforts ? C’est là qu’intervient le cross-platform : une méthode qui consiste à développer une seule base de code pour cibler plusieurs systèmes d’exploitation, principalement Android et iOS.

Le développement cross-platform présente ainsi de nombreux avantages. Il permet avant tout de réduire considérablement les coûts et les efforts de maintenance, puisqu’une seule base de code est nécessaire, limitant ainsi les corrections de bugs et les mises à jour multiples. Il offre aussi un déploiement plus large et plus rapide, en touchant un public plus vaste sans avoir à développer des applications distinctes.

Comme notre développeur n’était pas initialement spécialisé dans le développement mobile, il a dû se former pour maîtriser les outils nécessaires. Deux technologies principales se sont imposées dans notre réflexion : React Native et Flutter.

Nous avons donc mené une étude comparative afin de choisir la solution la plus adaptée à nos besoins. Après analyse, notre choix s’est porté sur Flutter. Pour plus de détails, vous pouvez consulter l’étude complète en suivant ce lien : https://framagit.org/wicklow/peertube-prototypes

Suite à ce choix, notre développeur s’est lancé dans l’apprentissage et la maîtrise de Flutter. Il vous partage son expérience à travers la suite de cet article.

Logo Flutter – Domaine public – Wikicommons.

Apprendre Dart et Flutter

La première étape a été d’apprendre le fonctionnement de Flutter. Les applications Flutter sont écrites en Dart, un langage orienté objet. J’ai donc commencé par explorer la documentation officielle de Dart et Flutter pour comprendre les bases.

Voici les différentes ressources que j’ai utilisées pour m’initier :

La documentation officielle de Flutter : une excellente introduction officielle pour comprendre les concepts fondamentaux de Flutter.

Le blog de Flutteris : des articles détaillés et des tutoriels pratiques pour approfondir mes connaissances.

Code with Andrea : des guides et des exemples concrets pour apprendre à structurer et optimiser mes projets Flutter.

Choisir son architecture

Une fois ces bases acquises, je me suis penché sur la structure idéale pour le projet. Après avoir exploré différentes approches, j’ai opté pour une architecture “Feature First”. Cette méthode consiste à organiser le code par fonctionnalités plutôt que par type de fichier (comme les modèles, les vues, ou les contrôleurs).

L’approche “Feature First” offre de nombreux avantages. Elle apporte une clarté accrue au projet en isolant chaque fonctionnalité dans son propre dossier, ce qui simplifie la navigation et la compréhension de la structure du code. De plus, cette méthode favorise la modularité en rendant les fonctionnalités indépendantes, permettant ainsi leur réutilisation ou leur modification sans affecter les autres parties du projet. Enfin, dans le cadre d’un projet libre, cette organisation facilite la contribution des développeurs externes, qui peuvent se concentrer sur des fonctionnalités spécifiques sans interférer avec le reste du code.

Choisir ses dépendances

Chaque bibliothèque intégrée dans le projet doit être remise en question dans le but de s’assurer qu’elles répondent aux exigences fonctionnelles, qu’elles soient maintenables à long terme et qu’elles n’introduisent pas de risques techniques. Flutter étant une technologie encore jeune, il est important d’être prudent dans le choix des dépendances. Certaines bibliothèques peuvent manquer de maturité ou de soutien communautaire, ce qui pourrait entraîner des bogues ou des problèmes de mises à jour futures.

Voici quelques critères généraux pour choisir une dépendance sur pub.dev :

Vérifiez le nombre de personnes contribuant activement au projet sur GitHub. Une équipe de contributeur⋅rices plus importante indique souvent un projet plus robuste à long terme.

Assurez-vous que le projet est actif, avec des commits récents et des demandes régulières, de préférence de la part d’utilisateur⋅rices différent⋅es.

Regardez le score global sur pub.dev, qui mesure la qualité des paquets.

Vérifiez la fréquence des publications.

Donnez la préférence aux paquets publiés par un⋅e éditeur⋅rice vérifié⋅e.

Les dépendances choisies

En prenant en compte ces critères, j’ai soigneusement sélectionné les bibliothèques nécessaires pour le développement de l’application mobile PeerTube.

Voici les principales bibliothèques retenues pour le projet, accompagnées des réflexions qui ont guidé leur sélection :

Le gestionnaire d’état

Un gestionnaire d’état (ou “state management” en anglais) est une approche ou un outil utilisé pour gérer l’état d’une application. Dans le contexte de Flutter, l’état fait référence aux données dynamiques ou aux informations qui peuvent changer au cours de l’exécution de l’application, comme l’entrée utilisateur, les données récupérées d’une API, ou encore l’état d’une animation.

Plusieurs approches et bibliothèques sont disponibles pour la gestion des états dans les applications Flutter, chacune avec ses propres avantages et limitations.

Approches de gestion d’état

StatefulWidget : le mécanisme intégré le plus simple pour gérer l’état local.

InheritedWidget : une solution native qui permet de partager l’état entre les widgets.

Variables globales : une approche où l’état est stocké dans des variables globales qui sont accessibles dans toute l’application.

Les bibliothèques populaires

Provider :

Pour : simple, léger et largement utilisé par la communauté Flutter.

Contre : nécessite un code standard et manque de fonctionnalités avancées.

Bloc :

Pour : hautement structuré, il favorise la séparation des préoccupations et est idéal pour gérer une logique d’application complexe.

Contre : courbe d’apprentissage abrupte. Nécessite plus de code de base que les autres solutions.

Riverpod :

Pour : une alternative moderne à Provider avec une API plus simple, une meilleure testabilité et la prise en charge de l’injection de dépendances. Elle supprime les contraintes de l’arbre des widgets de Flutter, ce qui la rend plus flexible.

Contre : elle est plus récente que les autres bibliothèques, mais son développement est très actif.

Riverpod a été choisi pour sa simplicité, sa flexibilité et son évolutivité, offrant une gestion globale de l’état indépendante de l’arbre des widgets. L’approche de Riverpod avec des variables globales me convient. En outre, l’intégration de la génération de code dans le projet promet d’améliorer les performances à l’avenir. Cependant, il est important de choisir une solution qui correspond à vos préférences et à vos besoins spécifiques.

Le routeur

Un routeur est un composant essentiel qui gère la navigation entre les différents écrans d’une application, permettant de définir les itinéraires, de gérer les transitions, de transmettre des paramètres via les URL, et de prendre en charge les liens profonds.

Plusieurs bibliothèque sont disponibles pour gérer la navigation dans les applications Flutter :

Navigator : inclus dans le SDK Flutter mais manque de fonctionnalités telles que les liens profonds.

AutoRoute : permet de générer du code pour simplifier la gestion des itinéraires, mais peut être difficile à configurer.

GoRouter : une bibliothèque officielle soutenue par l’équipe Flutter, combinant la facilité d’utilisation avec un support avancé de liens profonds.

Après analyse, GoRouter s’est avéré être le meilleur choix pour ce projet, en particulier pour :

Un support actif de l’équipe Flutter et une documentation complète, garantissant un choix fiable à long terme.

La prise en charge des liens profonds, essentielle pour rediriger les utilisateurs entre les pages web des plateformes PeerTube et l’application.

Basé sur l’API Navigator 2.0 incluse dans le SDK Flutter.

Le lecteur vidéo

Le lecteur vidéo est un composant essentiel pour l’application PeerTube, car il constitue le cœur de l’expérience utilisateur. Il était crucial pour nous de faire le bon choix de bibliothèque dès le début pour garantir une lecture fluide, une compatibilité avec différents formats vidéo, et une intégration harmonieuse avec les fonctionnalités de l’application.

Plusieurs bibliothèques sont disponibles et utilisées par la communauté Flutter pour implémenter la fonctionnalité de lecture vidéo :

video_player : une bibliothèque officielle soutenue par l’équipe Flutter qui fournit des fonctionnalités de lecture vidéo de base, mais nécessite une personnalisation importante pour les fonctionnalités avancées.

Chewie : une puissante surcouche autour de video_player soutenue par la communauté Flutter qui fournit une interface de lecteur pré-construite et hautement personnalisable et prend en charge les commandes de base telles que lecture/pause, le plein écran et les sous-titres.

BetterPlayer : un lecteur vidéo avancé construit au-dessus de Chewie, avec des fonctionnalités supplémentaires, mais pas très bien maintenu.

MediaKit : un ensemble plus récent qui vise à fournir une expérience de lecture vidéo cohérente et riche en fonctionnalités sur toutes les plateformes, bien que son écosystème et le soutien de la communauté soient encore en cours de maturation.

Après analyse, Chewie s’est avéré être notre premier choix en raison de son équilibre entre simplicité et fonctionnalité :

Facile à intégrer, il fournit une interface de lecture prête à l’emploi avec une configuration minimale.

Personnalisable, permettant aux développeurs d’adapter l’interface utilisateur et le comportement aux besoins de l’application.

Maintenu par la communauté Flutter, il garantissait sa fiabilité.

Cependant, après plusieurs mois d’utilisation, nous avons constaté que la maintenance de Chewie était plus faible que prévu. Certaines fonctionnalités et corrections de bugs présentes dans des merge requests n’étaient pas intégrées, ce qui a limité notre capacité à répondre rapidement aux besoins de l’application.

En conséquence, nous avons décidé de migrer vers video_player. Bien que cette bibliothèque nécessite plus de temps pour mettre en place une interface utilisateur personnalisée et des fonctionnalités avancées, elle offre un contrôle plus granulaire et une stabilité accrue. Cette transition nous a permis de concevoir une expérience utilisateur sur mesure tout en garantissant une meilleure fiabilité à long terme.

Les flavors

Nous avions besoin de deux applications distinctes :

stable : la version en production, destinée à être déployée sur les stores publics.

nightly : la version intégrant les derniers changements, basée sur la branche de développement.

Les flavors permettent de gérer ces deux versions de manière distincte, en créant deux applications séparées. Pour mettre cela en place, il est nécessaire de configurer les flavors sur chaque plateforme native ciblée (Android et iOS). De plus, chaque application doit être signée séparément pour chaque flavor. Enfin, le code Dart partagé par toutes les plateformes peut être configuré en fonction de l’environnement grâce à l’argument --dart-define-from-file, qui permet de fournir un fichier .env contenant les variables nécessaires.

Exemple de commande pour construire l’environnement stable :

flutter build apk --flavor stable --dart-define-from-file=env-stable.json

Pour plus de détails sur la configuration des flavors et la signature des applications, consultez ce rapport dans lequel nous détaillons comment nous avons mis en place ce système de flavor.

Les erreurs commises

Écrire des tests trop tôt

L’une des erreurs que j’ai commises au début du projet a été de vouloir écrire des tests unitaires et d’intégration dès les premières étapes du développement. Bien que les tests soient essentiels pour garantir la qualité et la stabilité du code, les écrire trop tôt peut s’avérer contre-productif, surtout lorsque l’architecture et les fonctionnalités de l’application ne sont pas encore clairement définies. En effet, au début du projet, de nombreux changements ont été apportés à la structure du code et aux fonctionnalités, rendant ainsi les tests obsolètes rapidement.

De cette expérience, j’ai retenu la leçon suivante : il faut accorder la priorité à la stabilité de l’architecture. Avant d’écrire des tests, il est crucial de s’assurer que l’architecture de l’application est bien définie et stable. Cela permet de réduire le nombre de modifications fréquentes des tests.

Sous-estimer la complexité des dépendances

Nous avons initialement intégré plusieurs bibliothèques sans évaluer pleinement leur maturité et leur compatibilité avec notre projet. Certaines dépendances se sont avérées instables ou mal maintenues, ce qui a entraîné des problèmes imprévus et des retards. Une analyse plus approfondie des dépendances aurait permis d’éviter ces écueils.

Ce parcours d’apprentissage de Flutter m’a ainsi permis de poser des bases solides pour développer l’application mobile PeerTube.

Dans le prochain article, nous aborderons une étape cruciale : la publication de l’application sur les stores (Google Play, App Store et F-Droid). Nous y détaillerons les démarches, les bonnes pratiques et les pièges à éviter pour réussir la mise en ligne d’une application mobile telle que PeerTube.

Nous en profitons pour vous rappeler que le financement participatif pour le développement de l’application PeerTube est en cours jusqu’au 17 juin 2025 !