<![CDATA[Alexis L]]>https://alexisl.fr/https://alexisl.fr/favicon.pngAlexis Lhttps://alexisl.fr/Ghost 5.118Fri, 20 Mar 2026 08:23:28 GMT60<![CDATA[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'

]]>
https://alexisl.fr/codolingo/6815d2c2fdca9600017ac262Sat, 03 May 2025 19:21:04 GMT

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

Comment on a créé Codolingo

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.

Comment on a créé Codolingo

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.

Comment on a créé Codolingo

Application

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

Comment on a créé Codolingo

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.

Comment on a créé Codolingo

Architecture

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

L'UI

Comment on a créé Codolingo

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

Comment on a créé Codolingo

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

Comment on a créé Codolingo
  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.

Comment on a créé Codolingo

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.

Comment on a créé Codolingo

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 !

]]>
<![CDATA[A small tutorial for Git Annex, the Git LFS alternative]]>Git Annex is a powerful tool that allows you to manage large files in your Git repositories without storing them directly in the Git history. With Git Annex, you are more free than with Git LFS as you have the entire control of where your files are stored. For example,

]]>
https://alexisl.fr/git-annex-the-git-lfs-alternative-2/66b69327016693000166497cSun, 18 Aug 2024 09:40:52 GMT

Git Annex is a powerful tool that allows you to manage large files in your Git repositories without storing them directly in the Git history. With Git Annex, you are more free than with Git LFS as you have the entire control of where your files are stored. For example, with Git LFS, it is mandatory to store your files in the same service that you use to store your code (e.g. Github LFS if you store your files on Github), but with Git Annex, you can choose where to store it without any limitation.

Initializing Git Annex

Before you can start using Git Annex, you need to download it for your OS using the official website: https://git-annex.branchable.com/install/

Cloning the Project

To clone your project, use the following command:

git clone [PROJECT]

Initializing Git Annex

Once you have cloned the repository, you need to initialize Git Annex. You can give your device a name (this is optional but recommended for better organization):

git annex init "DEVICE_NAME"

Configuring .gitattributes

To manage the limits between files stored in Git and those in Git Annex, add a .gitattributes file with the following content:

annex.largefiles=(largerthan=50MB) # Limit file size for Git

Here, the files that are larger than 50MB will be sent automatically to git annex instead of git by using git annex add .

Setting Up a Remote

Next, git annex have to know where to store our large files. For this example, we will use a directory:

git annex initremote REMOTE_NAME type=directory directory=PATH_TO_DIRECTORY encryption=none

By doing this, all the large files will be sent to a repository after a sync.

Adding Files

To add files to Git Annex, use the following command:

git annex add .

This command will track all files in the current directory. If a file is smaller than the limit you put in your .gitattributes file, it will be added automatically to Git

Synchronizing Files

To synchronize the files added to Git Annex with their respective remotes, use:

git annex sync --content

The --content option ensures that the files are sent to their respective remotes.

You can also add the -m parameter to add a commit message.

Setting Up for a Second User

If you have another user who needs access to the repository, they should repeat the cloning and initialization steps. They will also need to enable the remote:

git annex enableremote source directory=PATH_TO_DIRECTORY

Additional Information

  • All user computers are considered remotes in Git Annex.
  • The command git annex sync --content performs a git annex pull followed by a git annex push. If there are conflicts, Git Annex resolves them automatically by sending both versions of the file, with the filename differing slightly.
]]>
<![CDATA[The Jackbox Utility's state, or why a small update can take days to be published]]>It's July 29, 2024, and the Jackbox team is publishing their new tool called the Jackbox Megapicker. This app aims to launch Jackbox games faster without navigating through a pack menu.
Sound familiar? Yes, Jackbox Utility does the same... But in a different way. That’s where

]]>
https://alexisl.fr/the-jackbox-utilitys-state-or-why-you-should-think-twice-before-adding-a-library-2/66abe50b01669300016647c5Mon, 05 Aug 2024 22:37:48 GMT

It's July 29, 2024, and the Jackbox team is publishing their new tool called the Jackbox Megapicker. This app aims to launch Jackbox games faster without navigating through a pack menu.
Sound familiar? Yes, Jackbox Utility does the same... But in a different way. That’s where the problems began.

A new way to launch games

Jackbox Games create their party pack using their own engine called "Talkshow". This engine compiles games into a list of .jet (JSON files) and .swf files (the now-defunct Adobe Flash format).

To enhance speed, Jackbox Utility modifies these files by removing unnecessary elements, known as "loaders." This allows users to skip the title screen and menu, launching games with a single click. Initially, this method worked well since the games were not updated post-release. However, just a week before the Megapicker's launch, Jackbox updated all their games to implement a new way of launching games without modifying any files.

After the update, we found ourselves replacing files with outdated versions, causing crashes upon game startup.


To fix this mess, we had two choices :

  • Update all of our loaders (over 50 files).
  • Adopt the new method used by the Megapicker, which involves using a parameter given to the party pack to launch games faster.

As these two choices have downsides, we decided to choose both of them. By default, Jackbox Utility would use the new parameter, as it’s officially supported by Jackbox Games. However, for some games where this method doesn't work, we would continue using our loaders until Jackbox addressed the issues.

Thanks to our testers, we quickly integrated this new functionality. But, there was something unexpected that prevented us to update the app.

The responsible library

The Jackbox Utility's state, or why a small update can take days to be published

For every game, we show a description, images and most importantly, a video. Adding a video to the app is not something we can do natively for the engine we use, so we need to use a library.

The only library we found that was compatible with the major desktop platforms was media_kit, a cross-platform video player and audio player for Flutter and Dart.

When we attempted to add this library, we encountered issues with our operating systems:

  • Linux: A native library needs installation, which isn’t possible as the app isn’t bundled.
  • Windows: The app size doubled after adding the library.

Even with these issues, we decided to add the library as it was a great addition to Jackbox Utility and because the majority of the users are on Windows.

It worked great, until something clearly unexpected happened.

A small Windows compiler update

Shortly after our last release, the Visual Studio compiler received an update that altered behaviors related to a DLL called msvcp140, required by media_kit. This change caused the app to crash on Windows when launched.

We were not the only one with this problem, as an issue was created for it on the repository of Github actions and on media_kit. To resolve this issue, it was mandatory to define an internal variable in the library which the library owner quickly addressed. Despite this, crashes persisted in parts of the app not using the library. We suspected another issue related to the DLL, which seemed to vanish when we removed it.

As we needed to push the update fast, we decided to remove the video library so we could build an update. By doing this, we successfully removed the dll that was previously needed by the app, and it worked perfectly.

However, there was another problem that prevented us to publish the update, and it was still linked to this dll.

The Auto-updater

To keep the application up-to-date, we are using our home-made tool, the auto-updater. The complexity with it is that it can't be updated (or we should have an auto-updater for the auto-updater). If the app is not up-to-date, it will download the newest version and extract it to a folder called "app". The only problem with this behavior is that it doesn't delete the old version first, that means that if a file was present in the old version and is deleted in the new version, then it will still be present.

In our latest version, the msvcp140.dll was included. With the auto-updater’s behavior, this DLL wouldn’t be removed post-update. To resolve this, we created a new tool, media_kit_remover, to delete faulty files and restart the utility.

That way we were able to successfully remove this dll from the folder, and the utility was working fine, so we launched the 1.4.0 version of the app 🎊

The return of the DLL

As we published the latest version, everything was working fine, and it was wonderful to see all the users trying the new version. But, for some of them, the app crashed as if the dll was still in the app folder.

The strange part is that it happened only to some users that were on Windows 10 and that were using the app for the first time on the latest version. So we searched a lot and we found two things :

  • The problem fixed itself when you download the flutter tools to build the app
  • We were able to reproduce the problem using a windows fresh installation

After a few hours of testing and asking ourselves what was the problem, we tried to create a dump of the crash by using WinDbg and changing values in the Windows registries. The dump in hand, we found that there was still a mention of the msvcp140.dll even though we removed it. Then, we found out that the System32 folder has a version of the dll in it and that the app was using it. So, we tried to take the dll from a user with a working app and put it in the app folder of a user with a faulty app and... It worked flawlessly!

Conclusion

With this information, we determined that the users experiencing issues likely had an outdated version of msvcp140. Flutter seems to first search for the DLL in its folder before checking the System32 directory.

Armed with this knowledge, we can now include the latest version of the DLL in the app to ensure functionality.


I want to thank the Jackbox Utility team for helping me to get that update ready for all the users. Special thanks to Jaywalker for helping identify the source of the bug.

And thank you for reading until the end!

]]>
<![CDATA[A step into Wakapi : a Wakatime alternative]]>Like many developers, I stumbled upon Wakatime, a web app designed to track the time you spend working on projects. It's an amazing tool with plenty of extensions that allow you to monitor your time across various platforms, such as your IDE, your browser, and even while using

]]>
https://alexisl.fr/a-step-into-wakapi-a-wakatime-alternative/6696a6320166930001664686Wed, 17 Jul 2024 17:24:40 GMT

Like many developers, I stumbled upon Wakatime, a web app designed to track the time you spend working on projects. It's an amazing tool with plenty of extensions that allow you to monitor your time across various platforms, such as your IDE, your browser, and even while using Blender! However, using the free tier of Wakatime does come with some downsides. The most significant limitation is the code stats history, as you can't view more than two weeks of data without subscribing for $12 per month.

As I started searching for a self-hostable alternative, I came across two projects:

  • Hakatime, a server implementation of Wakatime with a good UI but seemingly no longer maintained.
  • Wakapi, another server implementation that appears to be more popular (with 2k stars) and highly configurable.

So let's dive into Wakapi!

Installation and configuration

💡
Before installing Wakapi, you can try it by using the official public instance found here

Installing Wakapi is really straightforward as you can install it using a one line script, eget or Docker.

Device configuration

After the installation, and creating your first account on the website, you can configure your pc to send data to Wakapi. The only thing you need to do is to create a .wakatime.cfg file in your home folder and add the api_url and your api_key following this template. Your api_key can be found by clicking on your profile in Wakapi and then clicking on "Show API key".

💡
In case your key is stolen, you can always generate a new one in the Settings. You can only have one api key!

By doing this, all the extensions that send heartbeats to Wakatime will start sending them to your wakapi instance instead!

Instance configuration

You can also configure your instance using environment variables. A comprehensive list is available in the project README to customize Wakapi to your needs.

If you want your instance to be private or shared only with known people, you should set the WAKAPI_ALLOW_SIGNUP to false.  If you need to invite someone to your instance even when sign-up is disabled, you can always send them a private link to let them create an account.

Wakatime importation

Wakapi allows you to import all your data from Wakatime into your instance using a Wakatime API key. To do this, simply navigate to your settings and select the integrations section. There, you will be able to import your data and even send your Wakapi data back to Wakatime.

⚠️
Once your import is complete, you might notice a discrepancy between the time recorded on Wakatime and the time displayed on Wakapi. This difference arises because Wakapi calculates time differently than Wakatime. For more details, you can refer to the Wakapi FAQ

Statistics and a lot more

When you open Wakapi, you'll find all your stats displayed in various charts. There's even an activity chart, similar to GitHub's commit chart, that shows your daily activity!

But the statistics aren't limited to just you; you can also share them using badges or GitHub README stats. These generated images can be embedded on any website.

A step into Wakapi : a Wakatime alternative
Example of badges generated using Wakatime statistics

Labels and projects

All your data is automatically associated with a project, usually named after your working folder. You can always rename these projects in your account's data settings. Additionally, you have the flexibility to rename languages, operating systems, editors, and even machines.

If multiple projects are related, you can assign a label to them. This makes it easier to organize and sort them in your statistics tab.

Leaderboard

Last but not least, there's also a leaderboard where you can see how many hours your friends have worked in a week. It doesn't offer any additional functionality but does introduce a competitive element to your development journey.

A step into Wakapi : a Wakatime alternative
Example of the leaderboard

In summary, Wakapi is a self-hostable alternative to Wakatime that provides extensive customization, easy data import, and a visually appealing statistics view. It's an excellent choice for those who want to maintain control over their tracking data!

]]>