Pour aller plus loin
Tu as le cœur en main. Tu sais ouvrir un fichier inconnu, nommer ce que tu vois, et suivre une donnée depuis un bouton jusqu'à la base. Voici maintenant la carte des sujets avancés : pas un cours complet sur chacun, mais le pourquoi, le quand ça compte, et le fichier par lequel commencer le jour où tu voudras vraiment creuser.
Toute la couche données — la base locale et le backend — a déjà ses deux modules dédiés : le module WatermelonDB & l'offline-first et le module Supabase : le backend. On ne va donc pas les redétailler ici. Cette page couvre le reste : la réactivité, les animations, les graphiques, les tests, la surveillance en production, le coach IA — puis une vraie feuille de route pour continuer à apprendre seul. Pour chaque sujet : ce que c'est, pourquoi ton app l'utilise, et où regarder. Garde-les pour le jour où tu buteras précisément dessus : tu n'as besoin d'aucun pour lire 90 % du code.
1. Les Observables (RxJS) — des données qui se mettent à jour seules
Reviens un instant sur le module de traçage de bout en bout : on y a chargé un workout une fois, avec une promesse. Une promesse, c'est un événement ponctuel : « va chercher cette donnée, et quand tu l'as, donne-la-moi ». Une fois résolue, c'est fini — si la donnée change ensuite en base, ton écran ne le sait pas. Il faudrait recharger à la main.
Un Observable répond à un autre besoin. Au lieu d'une seule valeur livrée une fois, c'est un flux : un robinet qui ré-émet une nouvelle valeur à chaque fois que la donnée sous-jacente bouge. Tu t'abonnes une fois, et tu reçois ensuite toutes les mises à jour, automatiquement, tant que tu écoutes. C'est la bibliothèque RxJS qui fournit cette mécanique, et c'est ce que la base locale expose pour ses requêtes.
Une promesse, c'est commander une pizza : tu attends, elle arrive une fois, et la transaction est close. Un Observable, c'est un abonnement à un magazine : tu t'inscris une seule fois, puis chaque nouveau numéro arrive tout seul dans ta boîte aux lettres, jusqu'à ce que tu te désabonnes. Pour une app où les données changent (tu ajoutes une série, ton historique se met à jour), l'abonnement est exactement ce qu'on veut.
Le pont entre ce flux RxJS et le monde de React, c'est un petit hook maison : useObservable.
React ne sait pas s'abonner à un Observable tout seul ; ce hook s'en charge — il s'abonne quand le
composant apparaît, met à jour un état React à chaque émission (ce qui déclenche un re-render, comme on
l'a vu dans le module sur useState & useEffect), et se désabonne proprement quand le
composant disparaît.
// Transforme un flux RxJS en une simple valeur d'état React.
// `source` est l'Observable (ex. une requête WatermelonDB qui « observe » une table).
function useObservable<T>(source: Observable<T>, valeurInitiale: T): T {
const [valeur, setValeur] = useState<T>(valeurInitiale);
useEffect(() => {
// On s'ABONNE : à chaque nouvelle émission du flux, on met l'état à jour
// -> React ré-exécute le composant avec la donnée fraîche.
const abonnement = source.subscribe((suivante) => setValeur(suivante));
// Nettoyage : quand le composant disparaît (ou que `source` change),
// on se DÉSABONNE pour ne pas fuir de la mémoire.
return () => abonnement.unsubscribe();
}, [source]);
return valeur;
}
Le composant ne « tire » plus la donnée : c'est la donnée qui le « pousse ». L'écran reste à jour sans un seul rechargement manuel.
C'est ce qui fait qu'après avoir loggé une série, ton historique se rafraîchit tout seul :
l'écran d'historique observe la table des workouts ; dès que la base change, le flux émet, et la
liste se redessine. Pour le contexte complet sur d'où viennent ces flux et comment la base les
produit, c'est le module WatermelonDB & l'offline-first qui détaille la couche données.
Le fichier de départ ici, c'est hooks/ui/useObservable.ts.
2. Le protocole de synchronisation, en détail
Le module Supabase : le backend t'a expliqué pourquoi il faut synchroniser : tes données vivent d'abord dans le téléphone (offline-first), mais elles doivent aussi remonter dans le cloud pour survivre à une réinstallation et te suivre d'un appareil à l'autre. Ici, on regarde le protocole lui-même, c'est-à-dire la danse précise entre les deux côtés.
La synchronisation tourne autour de deux mouvements symétriques :
-
pull_changes— « tire » du serveur ce qui a changé depuis la dernière synchro réussie. Le client envoie un horodatage (« voici la dernière fois que j'ai parlé au serveur »), et le serveur renvoie uniquement les lignes créées, modifiées ou supprimées depuis. On ne retélécharge jamais tout — seulement le delta. -
push_changes— « pousse » vers le serveur ce que tu as fait localement hors-ligne : tes nouveaux workouts, tes modifications, tes suppressions. Le client envoie son paquet de changements, le serveur les applique dans la base partagée.
Que se passe-t-il si la même donnée a changé des deux côtés en même temps (tu modifies un workout sur ton téléphone pendant qu'une autre session l'a modifié ailleurs) ? Il faut une règle d'arbitrage. Halterofit applique la plus simple et la plus courante : last write wins — « la dernière écriture gagne ». On compare les horodatages, et la version la plus récente l'emporte ; l'autre est écrasée. Ce n'est pas la stratégie la plus fine qui existe (certaines apps fusionnent les changements champ par champ), mais pour un journal d'entraînement, c'est un compromis très raisonnable : simple, prévisible, et les conflits réels sont rares.
Dernier morceau du puzzle : le déclenchement. La synchro ne tourne pas en continu — ce serait gourmand en batterie et en réseau. Elle se lance en arrière-plan, à des moments opportuns : quand l'app revient au premier plan, quand le réseau réapparaît après une coupure, ou périodiquement. L'idée centrale, c'est que l'utilisateur ne voit jamais cette plomberie : il logge ses séries, et la synchro arrive « plus tard », sans bloquer l'écran.
Le chef d'orchestre côté client, c'est services/database/remote/sync.ts : c'est
lui qui appelle pull puis push, gère l'horodatage et applique les
changements reçus dans la base locale. Pour la moitié serveur du protocole (les fonctions
SQL et la sécurité qui les entoure), retourne au module Supabase : le backend.
3. Les animations & la performance
Sur mobile, la fluidité n'est pas un luxe : c'est une question de crédibilité. Un écran qui « saccade » (qui passe sous les 60 images par seconde) se ressent immédiatement et fait paraître l'app bon marché. Le défi, on l'a posé dans le module sur React Native : les briques natives : ton code JavaScript tourne sur un fil d'exécution, et l'affichage natif sur un autre. Si une animation doit traverser ce pont à chaque image, elle hoquette dès que le fil JavaScript est occupé.
Reanimated est la bibliothèque qui résout ce problème. Au lieu de calculer chaque image de l'animation en JavaScript puis de l'envoyer au natif, elle déclare l'animation une fois et la fait tourner directement sur le fil natif (le fil de l'interface). Résultat : même si ton JavaScript est occupé à charger des données ou à recalculer une liste, l'animation continue à 60 fps, imperturbable. C'est la différence entre une transition qui glisse et une transition qui « accroche ».
L'autre grand levier de performance, ce sont les listes. Une app de fitness affiche de longues listes : tout ton historique, tous tes exercices. Si tu dessinais réellement les 800 lignes d'un coup, tu exploserais la mémoire et le défilement deviendrait poussif. La solution s'appelle la virtualisation : ne dessiner que les quelques éléments réellement visibles à l'écran, et recycler ces « cases » au fur et à mesure que tu fais défiler. C'est le rôle de FlashList — une liste virtualisée très optimisée, qui garde le défilement fluide quelle que soit la longueur des données.
Sur un ordinateur de bureau, tu as de la puissance et de la mémoire à revendre ; une liste naïve passe souvent inaperçue. Sur un téléphone — surtout un modèle d'entrée de gamme — chaque milliseconde et chaque mégaoctet comptent, et l'utilisateur tient l'écran à 30 cm de ses yeux. La performance n'est pas une optimisation tardive : c'est un choix d'architecture qu'on prend au moment de choisir Reanimated et FlashList.
4. Les graphiques avec Skia
Un journal d'entraînement sans courbes de progression serait triste : voir sa charge au développé monter semaine après semaine, c'est la moitié de la motivation. Mais dessiner une courbe lisse, avec des points, des dégradés et des animations, à partir de composants d'interface classiques, c'est lourd et lent.
Skia est un moteur de dessin 2D bas niveau (le même qui dessine Chrome et Flutter), ici exposé à React Native. Au lieu d'assembler des dizaines de petits éléments d'interface, tu dessines directement sur un « canevas » : des chemins, des courbes, du remplissage. C'est du dessin haute performance — exactement ce qu'il faut pour des graphiques qui se redessinent en douceur quand les données changent, sans saccader.
Les composants de visualisation vivent dans components/charts/. Quand tu ouvriras ce
dossier, tu n'y verras pas du JSX d'interface classique mais des instructions de dessin
(des chemins, des coordonnées). C'est plus exigeant à lire que le reste de l'app, alors garde ce
sujet pour le jour où tu maîtrises déjà bien les composants ordinaires.
5. Les tests — lire la pyramide pour comprendre l'app
On parle souvent des tests comme d'un filet de sécurité — et ils le sont — mais voici une idée moins évidente, et précieuse pour toi : lire les tests est l'un des meilleurs moyens de comprendre ce que le code est censé faire. Un test décrit, en clair, une attente : « quand on appelle ceci avec cela, on doit obtenir ça ». C'est une spécification vivante, vérifiée à chaque exécution. Quand un fichier de code te semble opaque, va lire son test : tu y trouveras des exemples concrets d'utilisation et le comportement attendu, noir sur blanc.
Les tests se classent en une pyramide, du plus petit au plus large :
| Niveau | Outil | Ce qu'il vérifie |
|---|---|---|
| Unitaire | Jest | Une fonction isolée : « ce calcul de volume rend-il le bon nombre ? ». Nombreux, rapides. |
| Intégration | MSW | Plusieurs morceaux ensemble, avec un réseau simulé : MSW intercepte les appels et renvoie de fausses réponses, pour tester sans vrai serveur. |
| Bout en bout | Maestro | L'app entière : Maestro pilote l'interface comme un vrai doigt — tape ici, vérifie que cet écran apparaît. Peu nombreux, lents, mais proches du réel. |
Beaucoup de tests unitaires (rapides, ciblés) à la base ; quelques tests de bout en bout (lents, mais qui valident le parcours complet) au sommet. Si tu inversais — tout en bout-en-bout — ta suite mettrait des minutes et casserait au moindre détail. La forme en pyramide est un compromis entre vitesse et confiance.
La mise en place de ces outils (Jest, MSW, Maestro) et la façon de les lancer sont couvertes dans le
module sur l'outillage du projet. Pour lire un test, cherche les fichiers en
.test.ts à côté du code qu'ils vérifient, ou les flux Maestro dans leur dossier dédié.
6. La surveillance en production
Une app testée n'est pas une app sans bugs : une fois entre les mains de vrais utilisateurs, sur de vrais téléphones, avec de vrais réseaux capricieux, des erreurs surviendront que tu n'avais pas imaginées. Le problème : tu n'es pas là pour les voir. L'utilisateur, lui, voit juste un écran qui plante, hausse les épaules, et ne te dira jamais ce qui s'est passé.
Sentry est un service de surveillance des erreurs. Intégré dans l'app, il capte automatiquement les erreurs des vrais utilisateurs et te les envoie : le message, la pile d'appels, l'appareil, la version. Au lieu d'attendre qu'on te signale un bug, tu vois arriver un tableau de bord des crashs réels, classés par fréquence. C'est ce qui transforme « ça marche sur ma machine » en « ça marche pour mes utilisateurs ».
C'est un sujet de production plus que de lecture de code — tu n'as pas besoin de Sentry pour comprendre l'app — mais savoir qu'il existe explique certains imports et certaines initialisations que tu croiseras au démarrage de l'application.
7. Le coach IA — un sujet d'exploration future
Halterofit ne se contente pas d'enregistrer tes séances : il porte une couche de recommandations — un « coach » qui suggère des charges, des progressions, des ajustements. C'est la partie la plus jeune et la plus exploratoire de l'app, et probablement la plus fascinante à découvrir une fois que tout le reste te sera familier.
Cette logique vit dans services/coach/. Je te le signale comme une destination,
pas comme une étape : c'est le genre de dossier qu'on aborde en dernier, quand on connaît déjà
la couche données, l'état global et les écrans. Garde-le en tête comme la récompense de fin de
parcours.
8. Comment continuer à apprendre
Un guide qu'on lit une fois s'oublie ; une habitude reste. Voici une feuille de route concrète pour transformer ce que tu viens de lire en réflexe. Tu n'as pas besoin de tout faire d'un coup — pioche, répète, et reviens.
- Relis ce guide, mais autrement. Le premier passage sert à découvrir ; le second, à connecter. À la deuxième lecture, tu verras les ponts entre les modules — comment les hooks d'état nourrissent les écrans, comment l'état global relie des écrans éloignés, comment la donnée descend jusqu'à la base. Ce sont ces liens, pas les faits isolés, qui font le déclic.
- Trace une fonctionnalité par toi-même. C'est l'exercice le plus formateur du lot. Choisis une action de l'app (« je supprime un workout », « je change mon objectif ») et suis-la du bouton jusqu'à la base, écran par fichier. Le module sur le traçage de bout en bout t'en donne le modèle complet ; refais-le seul, sur une autre fonctionnalité.
- Lis un fichier inconnu par semaine. Pas plus. Un seul, choisi au hasard, abordé avec la grille du module sur la méthode pour lire un fichier inconnu : qu'est-ce qui entre, qu'est-ce qui sort, qui l'appelle, que fait-il. Nomme chaque morceau avec le module correspondant. En un trimestre, tu auras parcouru douze fichiers en profondeur — sans jamais te décourager.
- Lis les tests. Quand un fichier résiste, va voir son test. Comme on l'a dit, c'est la spécification du code écrite en exemples. Souvent, deux minutes de lecture de test éclairent ce qu'une heure de lecture de code laissait flou.
-
Lis les ADR. Le dossier
docs/decisions/contient les Architecture Decision Records : de courts documents qui expliquent pourquoi telle techno a été choisie plutôt qu'une autre. C'est de l'or pédagogique : tu y apprends non pas quoi fait le code, mais quels arbitrages ont mené là. Lire ces ADR, c'est entrer dans la tête de l'équipe au moment des choix.
La régularité bat l'intensité. Un fichier par semaine, lu à fond, te mènera plus loin qu'un week-end de bachotage suivi de trois mois de silence. Tu lis ce guide pendant tes shifts ; applique la même patience au code : petit, régulier, sans pression.
- Un Observable (RxJS) est un flux qui se met à jour seul, là où une promesse ne livre qu'une valeur ponctuelle ;
useObservableen fait un état React. - La synchro repose sur
pull_changes/push_changes, l'arbitrage « dernière écriture gagne », et un déclenchement en arrière-plan invisible. - Reanimated (animations sur le fil natif) et FlashList (listes virtualisées) gardent l'app à 60 fps ; Skia dessine les courbes.
- La pyramide de tests (Jest, MSW, Maestro) est aussi une documentation vivante : lis-la pour comprendre le code.
- Sentry remonte les erreurs réelles ;
services/coach/est la couche IA à explorer en dernier. - Pour continuer : relire, tracer seul, un fichier par semaine, lire les tests, lire les ADR.
Ne confonds pas une promesse et un Observable parce qu'ils « cherchent des données » tous les deux. La promesse répond une fois et se tait ; l'Observable continue d'émettre tant que tu es abonné. Si tu utilises une promesse là où il faudrait un Observable, ton écran affichera une donnée figée qui ne se rafraîchit jamais — et c'est exactement le genre de bug silencieux qui rend fou.
Aucun de ces sujets n'appartient à Halterofit. Les flux réactifs, la synchro avec arbitrage de conflits, l'animation hors du fil principal, la virtualisation des listes, la pyramide de tests, la surveillance en production : ce sont les piliers de toute application moderne sérieuse. En les nommant ici, tu t'es constitué un vocabulaire qui te suivra dans n'importe quel projet, sur n'importe quelle techno.
9. Tu es prêt
Récapitulons le chemin. Tu es parti d'une méthode pour aborder n'importe quel fichier. Tu as posé les fondations — les types, le modèle mental de React, les hooks d'état et d'optimisation. Tu as découvert le stack mobile — React Native, Expo, l'outillage. Tu as cartographié l'architecture — la structure, la navigation, les composants, l'état global. Tu as plongé dans les données — la base locale offline-first et le backend Supabase. Tu as tout relié par un traçage de bout en bout. Et te voilà au bout, avec la carte des territoires avancés en main.
Avec ça, tu peux ouvrir presque n'importe quel fichier de Halterofit et le décortiquer seul. Le meilleur entraînement, maintenant, tu le connais : choisis un fichier que tu n'as pas vu, applique la méthode, nomme chaque morceau. Plus tu le feras, plus ce sera rapide. Bon code. 💪
Tout ce que tu as appris — lire des types, comprendre des composants, suivre un état, distinguer les couches, tracer une donnée d'un bout à l'autre — n'est pas spécifique à cette application. C'est le socle du développement d'applications modernes. Tu n'as pas seulement appris à lire cette app : tu as commencé à penser comme un développeur. Et ça, personne ne pourra te le retirer. À bientôt dans le code.