Module 7 · Le stack mobile

Expo : la plateforme

Halterofit est une app React Native, mais tu ne verras presque jamais de code Xcode ni d'Android Studio en la lisant. La raison tient en un mot : Expo. C'est la couche qui prend en charge tout le « pénible natif » à ta place. Comprendre Expo, c'est comprendre pourquoi ce projet a la forme qu'il a — un seul dossier apps/mobile, un fichier app.json qui décrit l'app, et des builds qui se font « dans le nuage » sans que tu installes quoi que ce soit de lourd.

💡 Le fil rouge

React Native te laisse écrire ton interface en JavaScript/TypeScript, mais une app mobile, c'est aussi du code natif : compilation iOS/Android, accès à la caméra, aux notifications, aux capteurs, à l'écran… Tout ça, normalement, demande des outils lourds et des connaissances spécifiques à chaque plateforme. Expo est la plateforme qui gère cette partie native à ta place, pour que tu restes le plus possible dans le confort du JavaScript. C'est le grand thème de ce module : Expo = React Native sans (presque) la douleur native.

1. Qu'est-ce qu'Expo, au juste ?

Première confusion à dissiper : Expo, ce n'est pas une alternative à React Native. C'est une couche par-dessus React Native. React Native te donne les briques de base — des composants (View, Text, Pressable…) qui se transforment en vrais composants natifs iOS et Android. Mais React Native « nu » te laisse seul face à toute la machinerie autour : configurer le projet Xcode, le projet Android, installer et lier des modules natifs à la main, gérer les permissions, compiler, signer, distribuer… C'est précisément là qu'Expo intervient.

Expo est à la fois un framework et une plateforme. Comme framework, il fournit un grand catalogue de modules prêts à l'emploi (le « SDK Expo », qu'on détaille plus bas) pour accéder au natif depuis du JavaScript : notifications, image, audio, son, écran qui reste allumé, cryptographie, liens profonds… Comme plateforme, il fournit des services autour de ton app : la compilation dans le cloud (EAS Build), les mises à jour à distance (OTA), la soumission aux stores. Tu écris du TypeScript, et Expo orchestre le reste.

💡 L'analogie à garder en tête

Expo, c'est React Native sans (presque) la douleur de Xcode et Android Studio. Imagine que React Native te donne un moteur et quatre roues, mais que tu doives assembler toi-même la voiture, fabriquer le tableau de bord, et apprendre la mécanique des deux constructeurs. Expo te livre la voiture montée, avec un tableau de bord standard et un garagiste dans le cloud. Tu gardes le volant (ton code), tu perds la corvée du montage.

Pourquoi ce point compte tant pour lire Halterofit ? Parce que la quasi-totalité de ce que tu liras dans le projet est du TypeScript et du JSX. Tu ne croiseras presque jamais de fichier .swift, .kotlin, .gradle ou .xcodeproj — non parce qu'ils n'existent pas, mais parce qu'Expo les génère à partir d'une configuration déclarative. Le projet décrit ce qu'il veut (« je veux des notifications », « je veux du son »), et Expo fabrique le natif correspondant. Tu lis des intentions, pas de la plomberie.

2. Le spectre « managed → bare », et où se situe Halterofit

Historiquement, on présentait Expo comme un choix binaire entre deux mondes. À une extrémité, le mode managed (« géré ») : tu ne touches jamais au code natif, Expo gère tout, et ton app tourne dans une application générique appelée Expo Go (on y revient). C'est ultra-rapide à démarrer, mais limité : tu ne peux utiliser que les modules qu'Expo a déjà inclus. À l'autre extrémité, le mode bare (« nu ») : tu as un vrai projet React Native avec les dossiers ios/ et android/ ouverts, tu peux tout faire, mais tu retrouves toute la complexité native.

La réalité moderne — et celle de Halterofit — est plus subtile et bien meilleure que ce binaire. L'approche actuelle s'appelle le development build (ou « dev client »), avec le système des config plugins. L'idée est élégante : tu restes dans le confort déclaratif du mode managed (tu n'édites pas les dossiers natifs à la main), mais tu peux ajouter n'importe quel module natif custom. Les dossiers ios/ et android/ sont considérés comme « jetables » : ils sont régénérés à partir de ta config par une commande appelée prebuild. On parle de Continuous Native Generation (génération native continue).

Comment sait-on que Halterofit suit exactement cette voie ? Trois indices concrets, lus dans le vrai projet :

  • Le package.json liste expo-dev-client dans les dépendances. C'est la brique du development build.
  • Le app.json contient un bloc developmentClient (avec silentLaunch), preuve qu'on configure ce dev client.
  • Le app.json déclare une liste de plugins (config plugins) — par exemple expo-splash-screen, expo-notifications, expo-audio, le plugin Sentry, etc. Chacun injecte de la configuration native.
🏋️ Dans Halterofit

Halterofit utilise des modules qui n'existent pas dans Expo Go : par exemple react-native-mmkv (stockage rapide), @nozbe/watermelondb (base de données locale), @shopify/react-native-skia (graphismes), react-native-nitro-modules… Ce sont des modules natifs « custom ». Dès qu'une app en contient, Expo Go ne peut plus la faire tourner. Halterofit doit donc utiliser un dev client. Ce n'est pas un choix de style : c'est une conséquence directe de ses dépendances.

3. Expo Go vs dev client : la distinction qui piège tout le monde

C'est la confusion n°1 chez les gens qui découvrent Expo, alors prenons le temps. Les deux servent à tester ton app sur un téléphone pendant le développement, mais ils ne sont pas du tout interchangeables.

Expo Go est une application déjà compilée, que tu télécharges depuis l'App Store ou le Play Store. Elle contient un ensemble figé de modules natifs — ceux qu'Expo a décidé d'embarquer. Quand tu « lances » ton app dans Expo Go, tu ne compiles rien : Expo Go télécharge ton bundle JavaScript et l'exécute par-dessus son propre code natif. C'est magique de simplicité… tant que ton app n'a besoin que de modules natifs déjà présents dans Expo Go. Le jour où tu ajoutes un module natif qui n'y est pas, le code natif correspondant n'existe pas dans Expo Go, et ça plante.

Le development build (dev client) renverse la logique. Au lieu d'utiliser une app générique, tu compiles ta propre version d'Expo Go, taillée pour ton projet. Cette app custom contient exactement tes modules natifs à toi — react-native-mmkv, WatermelonDB, Skia, etc. — parce qu'ils ont été compilés dedans. Ensuite, elle se comporte comme Expo Go : tu modifies ton TypeScript, ça se recharge à chaud. Tu gardes la boucle de développement rapide, mais sans la limite des modules.

💡 Pourquoi le code natif ne peut pas « apparaître » à l'exécution

Le JavaScript se télécharge et s'exécute à chaud, oui. Mais le code natif (Swift/Kotlin/C++ d'un module) doit être compilé dans le binaire de l'app avant même de la lancer. Un module natif custom, c'est du code machine qu'il faut intégrer à la compilation. Expo Go étant déjà compilé et figé, il ne peut pas accueillir un nouveau module natif « à la volée ». D'où la règle : app avec modules natifs custom ⇒ dev client obligatoire. C'est exactement la situation de Halterofit.

 Expo GoDevelopment build (dev client)
Tu l'installes…depuis le store, tel quelen le compilant pour ton app
Modules natifsseulement ceux préinclus par Expotous les tiens, y compris custom
Démarrage initialinstantanéune compilation au départ
Recharge du JS à chaudouioui
Convient à Halterofit ?non (modules custom)oui

4. Le SDK Expo : la trousse à outils de Halterofit

Le « SDK Expo » est l'ensemble des modules expo-* que tu peux importer comme n'importe quel paquet JavaScript, et qui te donnent accès au natif sans écrire une ligne de Swift ou de Kotlin. En lisant le package.json réel de Halterofit, on voit précisément lesquels sont en jeu. Voici le tour du propriétaire, avec le rôle de chacun — c'est plus utile à retenir que des numéros de version.

  • expo-router — la navigation. C'est le routeur « basé sur les fichiers » : l'arborescence de ton dossier app/ définit les écrans et les URL, un peu comme les pages d'un site web. Il a son propre module dans le guide (le module sur la navigation), on ne fait ici que situer sa place dans l'écosystème Expo.
  • expo-notifications — les notifications locales et push. Halterofit s'en sert, par exemple, pour rappeler les temps de repos ou des rappels d'entraînement. Le module gère les permissions, l'affichage, et la réception.
  • expo-linking — les deep links (liens profonds). Il permet d'ouvrir l'app sur un écran précis depuis une URL externe (un lien halterofit://…), ce qui se marie avec expo-router et avec l'authentification.
  • expo-image — un composant image performant, avec cache, transitions et formats modernes. Plus rapide et plus malin que le <Image> de base de React Native.
  • expo-crypto — des primitives de cryptographie (hachage, valeurs aléatoires sûres). Pratique pour générer des identifiants ou sécuriser certaines valeurs sans dépendre d'une grosse librairie.
  • expo-constants — l'accès aux constantes de l'app : version, identifiants, et surtout les valeurs injectées via le bloc extra de la config (par exemple l'eas.projectId). C'est le pont entre app.json et ton code à l'exécution.
  • expo-keep-awake — empêche l'écran de s'éteindre. Détail crucial pour une app d'entraînement : pendant une séance, l'écran doit rester allumé entre deux séries, sinon l'utilisateur doit rallumer son téléphone toutes les 30 secondes, les mains pleines de magnésie. Un petit module, un énorme confort d'usage.
  • expo-audio — la lecture de sons. Utile pour les bips de fin de repos, les signaux sonores du chrono, etc. On voit d'ailleurs dans app.json qu'il est configuré avec microphonePermission: false — l'app joue du son mais ne demande pas le micro, ce qui évite une permission inutile et rassurante pour l'utilisateur.
  • expo-splash-screen — l'écran de démarrage (le logo affiché le temps que l'app charge). Configuré dans app.json avec la couleur de fond et l'image. On le contrôle aussi en code pour le masquer pile quand l'app est prête.
  • expo-auth-session — l'authentification via des fournisseurs externes (OAuth : Google, Apple, etc.). Il gère le va-et-vient navigateur ⇄ app, en s'appuyant justement sur les deep links d'expo-linking et le scheme déclaré dans la config.
🧭 Bon à savoir

Le package.json de Halterofit contient d'autres modules expo-* au service du build et de l'outillage plutôt que de fonctionnalités visibles : expo-dev-client (le development build vu plus haut), expo-build-properties (régler des détails de compilation iOS/Android, comme la version du SDK Android ou la cible iOS), expo-asset (gestion des ressources), expo-status-bar, expo-linear-gradient, expo-haptics (les petites vibrations), expo-network (état du réseau). Tu n'as pas à tous les connaître par cœur : retiens qu'ils suivent tous le même principe — un import JS qui ouvre une porte vers le natif.

5. Le fichier app.json : la carte d'identité de l'app

Si tu ne devais lire qu'un fichier pour comprendre comment l'app est configurée au niveau plateforme, ce serait app.json. C'est un fichier déclaratif : il ne fait rien, il décrit. Expo le lit et en déduit toute la configuration native. Voici un extrait réel, allégé, du app.json de Halterofit, commenté ligne à ligne.

apps/mobile/app.json
{
  "expo": {
    "name": "Halterofit",        // nom affiché sous l'icône
    "slug": "halterofit",        // identifiant court côté Expo/EAS
    "version": "0.1.0",          // version visible de l'app
    "orientation": "portrait",   // verrouillé en mode portrait
    "userInterfaceStyle": "dark",// l'app force le thème SOMBRE
    "scheme": "halterofit",      // schéme d'URL : halterofit://...
    "backgroundColor": "#0A0A0A",
    "icon": "./assets/icon.png", // l'icône de l'app
    "plugins": [ /* ... config plugins, voir plus bas ... */ ],
    "experiments": {
      "typedRoutes": true,       // routes typées (TS) pour expo-router
      "reactCompiler": true      // active le React Compiler
    },
    "developmentClient": { "silentLaunch": true } // on utilise un dev client
  }
}

Rien que dans cet extrait, on apprend énormément : l'app s'appelle Halterofit, elle est verrouillée en portrait, forcée en mode sombre, répond aux liens halterofit://, utilise des routes typées et le React Compiler (croisé dans le module sur les hooks d'optimisation), et tourne via un dev client. Une vraie carte d'identité.

Le bloc le plus important pour comprendre le natif est plugins : la liste des config plugins. Chaque entrée injecte de la configuration native au moment du prebuild. Un plugin peut être un simple nom ("expo-router") ou un couple [nom, options] quand on veut le paramétrer. Voici l'extrait réel correspondant.

apps/mobile/app.json
"plugins": [
  "expo-router",                      // navigation (forme simple : juste le nom)
  ["@sentry/react-native/expo", {     // suivi des erreurs (forme [nom, options])
    "organization": "halterofit",
    "project": "halterofit"
  }],
  ["expo-splash-screen", {            // écran de démarrage paramétré
    "backgroundColor": "#0A0A0A",
    "image": "./assets/splash.png",
    "imageWidth": 150
  }],
  ["expo-notifications", {            // notifications : icône + couleur
    "icon": "./assets/adaptive-icon.png",
    "color": "#0A0A0A"
  }],
  ["expo-audio", { "microphonePermission": false }] // du son, mais PAS de micro
]

Lis cette liste comme une liste de courses pour le natif : « ajoute le routeur, branche Sentry sur tel projet, fabrique un splash screen avec cette image, prépare les notifications avec cette icône, active l'audio mais n'exige pas le micro ». Au prebuild, Expo traduit chaque ligne en vraie configuration iOS/Android.

🧭 Le lien scheme ⇄ deep links ⇄ auth

Le "scheme": "halterofit" n'est pas décoratif : c'est lui qui rend les liens halterofit://… capables d'ouvrir l'app. C'est la pièce qui fait fonctionner expo-linking (deep links) et, par ricochet, le retour d'authentification d'expo-auth-session (le navigateur termine sur halterofit://… pour rendre la main à l'app). Trois morceaux qui ne sont compréhensibles qu'ensemble.

6. EAS Build : compiler dans le cloud

Reste une question : si Halterofit a besoin d'un dev client (donc d'une vraie compilation native), et si tu veux éviter Xcode et Android Studio… qui compile ? Réponse : EAS Build (Expo Application Services). C'est le service de compilation dans le cloud d'Expo. Tu lances une commande, Expo prend ton projet, le compile sur ses serveurs (machines macOS pour iOS, Linux pour Android), et te renvoie un binaire installable. Tu n'as aucun outil natif à installer localement. Pour un développeur seul, sur un PC Windows par-dessus le marché, c'est ce qui rend une app iOS possible tout court.

La compilation se pilote par un fichier eas.json, organisé en profils de build. Chaque profil décrit une manière de compiler. Voici le eas.json réel de Halterofit.

apps/mobile/eas.json
{
  "cli": { "version": ">= 16.26.0", "appVersionSource": "remote" },
  "build": {
    "development": {                 // profil de dev : produit un DEV CLIENT
      "developmentClient": true,
      "distribution": "internal"     // installation interne (pas le store)
    },
    "preview": {                     // version de test, proche de la prod
      "distribution": "internal"
    },
    "production": {                  // la vraie version pour les stores
      "autoIncrement": true          // incrémente le numéro de build tout seul
    },
    "production-apk": {              // variante : un .apk Android direct
      "extends": "production",       // hérite de "production"...
      "android": { "buildType": "apk" } // ...mais sort un APK
    }
  },
  "submit": { "production": {} }    // config d'envoi aux stores
}

On lit trois étages clairs. development fabrique le dev client (le seul qui a developmentClient: true). preview produit une app « comme en prod » mais distribuée en interne, pour faire tester. production est la version destinée aux stores. Bonus : production-apk étend la prod pour sortir un APK Android installable directement — un bel exemple d'héritage de config.

🧭 Bon à savoir : appVersionSource « remote »

Le "appVersionSource": "remote" dit « c'est EAS qui fait autorité sur les numéros de build », plutôt que de les coder en dur dans les fichiers. Combiné à autoIncrement sur le profil production, ça évite la corvée — et les erreurs — de gérer à la main le numéro de build à chaque envoi sur les stores.

7. Pourquoi Expo, pour un développeur solo ?

Halterofit est, pour l'essentiel, un projet d'une seule personne. Ce contexte change tout dans le choix des outils. Le temps et l'énergie sont la ressource la plus rare, et chaque heure passée à se battre avec la configuration native est une heure de moins sur la vraie app. Expo attaque ce problème de trois côtés.

  • La rapidité. Tu démarres une fonctionnalité en restant dans le JavaScript, avec recharge à chaud. Pas d'aller-retour incessant avec Xcode. La boucle « j'écris, je vois » reste courte.
  • Moins de configuration native. Les config plugins remplacent des heures de réglages manuels dans des fichiers iOS/Android obscurs. Tu déclares une intention dans app.json, Expo fabrique le natif. Et comme les dossiers natifs sont régénérables, tu n'as pas à les maintenir ni à les versionner.
  • Les mises à jour OTA (Over-The-Air). Comme une grande partie de l'app est du JavaScript, Expo permet d'envoyer une mise à jour du bundle JS directement aux téléphones, sans repasser par la validation des stores. Tu corriges un bug, tes utilisateurs l'ont en quelques minutes au lieu de plusieurs jours. (Attention : seul le JavaScript se met à jour ainsi ; changer du code natif — ajouter un module — exige toujours un nouveau build et un passage par les stores.)
💡 Le bénéfice de fond

Pour un solo, Expo déplace le centre de gravité du travail : de la plomberie vers le produit. Tu passes moins de temps sur « comment compiler » et plus sur « quelle fonctionnalité aide vraiment l'utilisateur ». EAS Build compile à ta place, les OTA distribuent à ta place, les config plugins configurent à ta place. Il te reste l'essentiel : écrire l'app.

8. Le lien avec l'outillage : Metro et Babel

Expo ne travaille pas seul. Sous le capot, deux outils transforment ton code avant qu'il ne tourne sur le téléphone, et Expo les pilote pour toi.

Metro est le bundler de l'écosystème React Native : il prend ton arbre de fichiers TypeScript/JSX, suit tous les import, et assemble le tout en un unique paquet JavaScript (le « bundle ») que le téléphone exécute. C'est aussi lui qui sert le rechargement à chaud pendant le développement. Babel, lui, est le transpileur : il convertit le TypeScript et le JSX modernes en JavaScript que le moteur peut comprendre. Détail parlant : c'est par un plugin Babel (babel-plugin-react-compiler, visible dans le package.json) que le React Compiler activé dans app.json entre réellement en action.

🔗 Où creuser

Metro et Babel ont leur propre traitement détaillé dans le module sur l'outillage — comment le bundle se construit, ce que fait le transpileur, comment le React Compiler s'insère dans Babel. Ici, retiens seulement la place de ces outils : Expo est le chef d'orchestre, Metro assemble, Babel traduit. Le React Compiler, lui, est aussi évoqué dans le module sur useContext, useMemo, useCallback et memo, car c'est lui qui automatise une partie de ces optimisations.

9. Lire la config comme un tout cohérent

Le vrai déclic, en lisant Halterofit, c'est de voir que app.json, eas.json et package.json se répondent. Une dépendance expo-* dans le package.json correspond souvent à un plugin dans app.json et à un comportement dans le code. Le scheme de app.json active les deep links qu'utilise expo-auth-session. Le profil development de eas.json produit le dev client qu'imposent les modules natifs custom du package.json. Tout se tient.

Quand tu ouvriras un fichier de config du projet, ne le lis pas isolément. Demande-toi toujours : « quel besoin de l'app cette ligne sert-elle ? ». La config n'est pas de la paperasse : c'est le mode d'emploi que tu donnes à Expo pour qu'il fabrique exactement l'app que tu veux.

✍️ Exercice de lecture

Voici un extrait réel du app.json de Halterofit. Lis-le, puis réponds.

apps/mobile/app.json
"scheme": "halterofit",
"userInterfaceStyle": "dark",
"plugins": [
  "expo-router",
  ["expo-audio", { "microphonePermission": false }]
],
"experiments": {
  "typedRoutes": true,
  "reactCompiler": true
}

Questions : (1) Que permet concrètement la ligne scheme, et quel module Expo en dépend ? (2) Pourquoi a-t-on mis microphonePermission: false sur expo-audio ? (3) Que t'apprend userInterfaceStyle: "dark" sur le thème de l'app ?

Voir le corrigé

(1) Le scheme déclare un schéme d'URL : halterofit://…. C'est ce qui permet à un lien externe d'ouvrir l'app (deep link). Le module qui repose là-dessus est expo-linking, et par extension expo-auth-session, qui ramène l'utilisateur dans l'app après une connexion OAuth en terminant sur une URL halterofit://….

(2) L'app joue des sons (bips de repos, signaux du chrono) mais n'a jamais besoin d'enregistrer. Mettre microphonePermission: false évite de demander une permission micro inutile — moins d'écrans de permission, plus de confiance de l'utilisateur, et un store moins regardant.

(3) L'app force le thème sombre, quel que soit le réglage du téléphone. C'est cohérent avec la couleur de fond #0A0A0A qu'on retrouve un peu partout (splash, fond, icône de notification). Halterofit assume une identité visuelle sombre.

🧠 Quiz éclair

1. En une phrase : qu'est-ce qu'Expo par rapport à React Native ?

Une couche par-dessus React Native (framework + plateforme) qui prend en charge la partie native pénible — compilation, modules natifs, accès aux capteurs/notifications, mises à jour — pour qu'on reste le plus possible dans le JavaScript. « React Native sans (presque) la douleur de Xcode et Android Studio. »

2. Pourquoi Halterofit ne peut-il pas tourner dans Expo Go ?

Parce qu'il utilise des modules natifs custom (MMKV, WatermelonDB, Skia, Nitro…) qui ne sont pas inclus dans Expo Go. Or le code natif doit être compilé dans le binaire : Expo Go étant figé, il ne peut pas les accueillir. Il faut donc un dev client.

3. À quoi sert expo-keep-awake, et pourquoi est-ce pertinent ici ?

À empêcher l'écran de s'éteindre. Crucial pour une app d'entraînement : l'écran doit rester allumé entre les séries, sinon l'utilisateur doit rallumer son téléphone sans arrêt en pleine séance.

4. Que décrit le fichier eas.json ?

Les profils de build pour la compilation dans le cloud (EAS Build) : development (le dev client), preview (test proche de la prod), production (les stores), plus une variante production-apk. Chacun décrit une manière de compiler.

5. Une mise à jour OTA peut-elle livrer un nouveau module natif ?

Non. L'OTA ne met à jour que le bundle JavaScript. Ajouter ou changer du code natif (un module) exige un nouveau build via EAS et un passage par les stores.

À retenir

Expo est une couche par-dessus React Native qui gère le natif à ta place (framework + plateforme). Halterofit utilise un development build (dev client) avec des config plugins, pas Expo Go, parce qu'il embarque des modules natifs custom. Le SDK Expo fournit les modules expo-* (router, notifications, linking, image, crypto, constants, keep-awake, audio, splash-screen, auth-session). app.json décrit l'app (nom, icône, splash, scheme, plugins, routes typées, mode sombre), eas.json décrit comment la compiler dans le cloud. Et tout ça repose sur Metro (bundler) et Babel (transpileur).

⚠️ Piège fréquent

Deux confusions vont de pair. La première : croire qu'Expo Go et le dev client sont la même chose. Ils ne le sont pas — Expo Go est une app figée du store, le dev client est ta version compilée avec tes modules. La seconde : croire qu'« utiliser Expo » limite ce qu'on peut faire au natif. C'était à moitié vrai à l'époque du tout-managed + Expo Go ; ce n'est plus le cas. Avec les development builds et les config plugins, tu as accès à tout le natif, exactement comme en React Native « nu » — Halterofit en est la preuve vivante, avec ses modules natifs custom.

🔄 Transférable

L'idée maîtresse dépasse Expo : une configuration déclarative (« je décris ce que je veux ») plutôt qu'impérative (« j'écris pas à pas comment le faire »), qu'un outil traduit ensuite en réalité. C'est le même principe que les fichiers d'infrastructure dans le cloud, les manifestes de conteneurs, ou les fichiers de CI. Lis d'abord la config d'un projet pour comprendre ses intentions : tu apprendras plus vite ce qu'il est qu'en plongeant tête baissée dans le code.