Comment on a créé Codolingo

Comment on a créé Codolingo

Dans le cadre de nos projets tutorés en dernière année d'école d'ingénieur, j'ai pu aider au développement de Codolingo, une application mobile permettant d'apprendre à programmer.

Dans ce projet, j'ai essayé de mettre en place un maximum de concepts de clean architecture que j'ai pu apprendre pendant mon stage et durant tout mon cursus d'ingénieur. Nous allons en parler dans cet article et voir pourquoi ils sont importants.

💻
Si vous préférez avoir du code sous la main plutôt que de lire des explications, l'application est open-source et disponible ici : https://github.com/Codolingo/mobile

Clean architecture et interface utilisateur

L'atomic design

L'atomic design consiste à séparer nos composants d'UI utilisés dans toute notre application en trois groupes distincts :

  • Les atomes, qui sont les éléments les plus petits de notre application, comme des couleurs, des champs, du texte ou des boutons
  • Les molécules, qui contiennent plusieurs atomes, comme une liste de boutons
  • Les organismes, qui contiennent plusieurs molécules et qui sont les composants les plus complexes de notre application. Cela peut être un dialogue, ou dans le cas de Codolingo, une partie de l'écran d'exercice

Ce concept est utilisé autant sur notre Figma, où nous créons tous les composants en amont, que sur notre code dans lequel on récupère tous ces composants et on les réintègre dans nos fichiers.

Figma

Sur notre Figma, chaque composant est créé un à un afin de pouvoir s'en servir dans les pages de notre application. Certains ont aussi des variantes qui permettent de changer leur style. Par exemple, pour les boutons de texte, il est possible de modifier leur couleur ainsi que leur état (désactivé) directement en modifiant leur variante.

Chaque composant est ensuite utilisé dans les écrans de notre application. De cette façon, si nous décidons de changer plus tard le style d'un bouton ou sa forme, il sera changé dans toutes les parties de notre application.

Application

Tous ces composants sont stockés dans un dossier components en fonction de leur groupe atomic design et ensuite selon leur type.

Certains composants peuvent être abstraits. Ils ne sont pas utilisés directement dans l'application, mais doivent être étendus par d'autres composants pour être exploités. C'est le cas du composant button qui permet d'ajouter le code nécessaire pour une surface cliquable. Ce bouton a un style, mais n'a aucun contenu, il devra être ajouté par les éléments qui l'étendent afin d'avoir un bouton avec du texte ou une icône par exemple.


Architecture

L'architecture de l'application suit un template Flutter permettant de séparer les différents éléments.

L'UI

Tous les écrans sont séparés en deux parties :

  • Une page, contenant les composants de notre écran
  • Un ViewModel, s'occupant de communiquer avec les données métier

En faisant cette séparation, notre page contiendra seulement des éléments graphiques et toute communication avec l'extérieur se fera à partir du ViewModel. Lorsque le ViewModel reçoit des données et doit les changer sur la page, il doit juste appeler la méthode notify afin que la page s'actualise.

Le Domaine

La partie domaine contient :

  • Des repositories appelés directement par les ViewModel et qui s'occupent d'appeler les services et les transformers
  • Des services qui permettent de récupérer des données externes, comme provenant d'une API
  • Des transformers qui permettent de modifier les données d'un type à un autre

Cette séparation est primordiale pour que les classes aient une seule responsabilité.

Exemple : Récupération de données météo

  1. Nous faisons une application de météo. Nous voulons récupérer les informations d'une api.
  2. On appelle WeatherRepository pour récupérer les données météo d'un endroit donné.
  3. Le WeatherRepository appelle OpenWeatherMapService qui va s'occuper de récupérer les données brutes de OpenWeatherMap
  4. Comme les données sont encore brutes, il faut les transformer pour pouvoir les utiliser dans notre application. C'est pour cela que nous appelons le WeatherTransformer qui se charge de transformer les données.
  5. Les données transformées sont ensuite retournées par le WeatherRepository.

Dossiers

Dans notre couche domaine (contenant les repositories, les services et les transformers), chaque élément est réparti dans un dossier en fonction de son type. Les services peuvent aussi avoir un sous-dossier, car il peut y avoir plusieurs services pour la même partie de l'application (on en parle juste après dans l'injection des dépendances).

Pour la partie UI, nous séparons nos différentes pages, mais gardons la page et le ViewModel dans le même dossier comme un ViewModel est lié à une seule page. Il est aussi possible d'ajouter un dossier components au cas où des composants seraient nécessaires pour une page précise.


Flavors et injection de dépendance

Un concept très utilisé en développement mobile est l'utilisation des flavors. Cela permet de changer le comportement de l'application en fonction d'un paramètre donné lors de sa compilation.

Pour Codolingo, nous avons 4 flavors différents afin de mieux contrôler le comportement de l'application :

  • Production : Permet de se connecter au serveur de production quand l'application est lancée
  • Staging : Permet de se connecter au serveur de préproduction
  • Debug : Permet de se connecter à un serveur local
  • Dummy : Permet d'utiliser l'application sans avoir besoin de serveur avec des données fixes

Chaque flavor a sa propre icône de couleur qui nous aide à reconnaître quelle application on utilise lorsqu'elles sont toutes installées sur le même téléphone.

L'application utilise un système d'injection de dépendance pour accéder aux repositories, services et transformers. Au démarrage de l'application, chacun de ces objets sera créé en fonction du flavor sélectionné.

Par exemple, nous utilisons une interface qui s'appelle api_service . Il existe deux classes qui implémentent cette interface :

  • En utilisant le flavor production, staging ou debug, live_api_service sera utilisé. Cela permet d'accéder aux vraies données provenant d'une api passée en paramètre.
  • En utilisant le flavor dummy, dummy_api_service sera utilisé. Cela permet de récupérer des données écrites dans un fichier json sur l'appareil à la place d'utiliser un serveur.

Pour faire cette injection, nous utilisons une classe abstraite appelée CodolingoProvider qui s'occupe de créer les injections pour les classes utilisées dans notre application. Chaque flavor l'étend pour injecter ses propres classes.


Conclusion

Aujourd'hui, je suis très content du résultat et de tout ce que l'on a pu réaliser ! L'application est jolie, le code est modulable et je me sens apte à continuer à la développer si le destin m'y amène. Le fait d'avoir pu lier tout ce que j'ai appris pendant mon cursus et mon expérience personnelle en fait un projet particulièrement intéressant et qui me tient à cœur !

Je sais qu'il me reste beaucoup de choses à apprendre sur l'architecture des applications mobiles et j'ai donc hâte d'en apprendre davantage sur ce sujet !