Skip to Content

Chapitre 12.5 — ⭐ EAS Update : les mises à jour OTA

Où on en est : Drill est installé en release sur ton téléphone. Voici la pièce qui rend la voie perso VIABLE : corriger un bug ou améliorer un écran sans rebuilder ni réinstaller — le déploiement web, dans une app native.

⏱️ TL;DR — Le ch. 1.2 l’avait promis : le bundle JS est remplaçable. EAS Update publie un nouveau bundle sur un channel ; l’app installée le télécharge à l’ouverture et l’applique au lancement suivant. Ce qui passe en OTA : TOUT le JS + les assets bundlés. Ce qui ne passe JAMAIS : le natif (modules, config, icône) — gardé cohérent par la runtimeVersion. Workflow : eas update --channel production -m "fix scoring" → tous les téléphones ont le fix demain matin.

🎯 Objectifs

  • Installer et configurer expo-updates (channels, runtimeVersion).
  • Publier une OTA et la voir arriver sur le téléphone.
  • Comprendre la frontière absolue OTA/natif et la runtimeVersion qui la garde.
  • Régler la politique de chargement (checkAutomatically, fallbackToCacheTimeout).

Le setup

npx expo install expo-updates eas update:configure # remplit app.config.ts (updates.url, runtimeVersion) + eas.json (channels)

Ce que la config ajoute :

// app.config.ts — ajouté par eas update:configure updates: { url: 'https://u.expo.dev/<projectId>', }, runtimeVersion: { policy: 'appVersion' }, // ← la clé de voûte, expliquée plus bas
// eas.json — chaque profil de build reçoit son channel "preview": { "channel": "preview", }, "production": { "channel": "production", }

Le modèle mental : un binaire est construit branché sur un channel (l’étiquette cuite dans l’APK). eas update --channel X publie un bundle sur cette étiquette : tous les binaires branchés sur X le récupèrent. Nos deux channels : preview (l’APK perso) et production (le futur Play Store) — on peut tester une OTA sur son téléphone avant de la pousser au monde.

Un rebuild s’impose une dernière fois (expo-updates est un module natif !) : eas build --profile preview --platform android --local → install. C’est le DERNIER rebuild « obligatoire » — tout le JS d’après passera par OTA.

Publier

eas update --channel preview --message "Timer : fix du pulse sous 5s"

La CLI bundle le JS (Metro, comme un build), uploade bundle + assets, publie sur le channel. Sur le téléphone : à la prochaine ouverture, l’app télécharge en arrière-plan, et applique au lancement suivant (comportement par défaut — réglable ci-dessous). Délai réel constaté : vous poussez à 23 h, le téléphone a le fix au café du matin.

La frontière absolue : OTA vs natif

Passe en OTA ✅Ne passe JAMAIS ❌ (→ rebuild + réinstall)
Tout votre TS/JS (écrans, logique, styles)Ajout/retrait d’un module NATIF
Assets bundlés (images require, sons)app.config.ts natif (icône, splash, permissions, scheme)
Corrections, features JS entièresMontée de SDK Expo / RN
Le fallback bundlé de questions (ch. 7.4)Changement de versionCode/signature

Le garde-fou est la runtimeVersion : l’app ne télécharge que les updates dont la runtimeVersion correspond à la sienne. Policy appVersion = la runtime suit version d’app.config.ts. La discipline qui en découle :

  • Changement natif (nouveau module, config) → bump version (1.2.0 → 1.3.0) → rebuild → les OTA suivantes ciblent 1.3.0, les vieux binaires 1.2.0 ne reçoivent RIEN d’incompatible. (Le schemaVersion du ch. 7.3, appliqué au conteneur — même idée.)
  • Changement JS pureas update, version inchangée.

Oublier le bump = pousser du JS qui appelle un module natif absent = crash au lancement chez tous les destinataires. C’est LE piège d’EAS Update — la runtimeVersion existe pour le rendre impossible, à condition de respecter la discipline du bump.

La politique de chargement

Le défaut (télécharger en fond, appliquer au prochain lancement) est le bon pour Drill : jamais d’attente utilisateur. Les réglages à connaître :

// app.config.ts updates: { url: …, checkAutomatically: 'ON_LOAD', // vérifier à chaque ouverture (défaut sain) fallbackToCacheTimeout: 0, // 0 = ne JAMAIS faire attendre (lancer l'existant) },

Et pour les cas « fix critique à appliquer TOUT DE SUITE », l’API impérative :

import * as Updates from 'expo-updates' const update = await Updates.checkForUpdateAsync() if (update.isAvailable) { await Updates.fetchUpdateAsync() await Updates.reloadAsync() // applique immédiatement (à proposer, pas imposer) }

Le pattern poli : un petit banner « Mise à jour prête — Redémarrer ? » sur l’écran d’accueil (jamais au milieu d’une partie !). Updates.updateId/createdAt alimentent l’écran « À propos » des Réglages — savoir quelle version JS tourne vaut de l’or en debug.

⚠️ Piège — En développement (Metro), expo-updates est inactif : les OTA ne se testent QUE sur un build release installé. Le rituel de test : build preview → install → eas update --channel preview → fermer/rouvrir l’app DEUX fois (téléchargement, puis application) → vérifier le changement. Et surveillez le dashboard expo.dev/updates : il liste qui a quoi. Plan gratuit : confortable pour un usage perso (limites sur le nombre d’utilisateurs actifs mensuels — largement au-dessus de votre famille).

💡 Pour un dev Next.js — C’est LE moment « le mobile rejoint le web » : eas updatevercel deploy. Votre réflexe « je fixe, je pousse, c’est en ligne » fonctionne enfin pour une app native — avec la nuance de la frontière natif (inexistante sur le web) et du double lancement. Pour vos apps perso, ça change TOUT : le coût marginal d’une amélioration tombe à zéro.

✏️ Exercices

1. Le rituel de test complet : build+install preview, changez un texte visible (le sous-titre de l’accueil), eas update --channel preview, et suivez l’arrivée (deux ouvertures). Vérifiez updateId dans un écran À propos improvisé.

✅ Solution

Après la 2e ouverture, le texte change ; Updates.updateId (affiché dans Réglages → À propos) passe de null/embedded à l’id de l’update. Si rien n’arrive : vérifier le channel du binaire (eas build:list — champ channel), la runtimeVersion des deux côtés (dashboard), et que l’app est bien un build release (pas Metro).

2. Scénario à trier : (a) fix du calcul de streak, (b) nouvel écran de stats, (c) ajout d’expo-camera pour un mode photo, (d) nouvelle icône, (e) nouveau thème de questions dans le repo GitHub. OTA, rebuild, ou ni l’un ni l’autre ?

✅ Solution

(a) OTA. (b) OTA (du JS pur, même un écran entier). (c) Rebuild + bump version (module natif). (d) Rebuild (l’icône est cuite au build). (e) Ni l’un ni l’autre : les questions vivent sur GitHub (ADR 0001) — un git push suffit, l’app les fetch. Trois canaux de mise à jour, trois vitesses : données (instantané), JS (heures), natif (réinstallation).

3. Implémentez le banner « Mise à jour prête » : vérification au focus de l’accueil, proposition de reload, jamais pendant une partie.

✅ Solution

const [ready, setReady] = useState(false) useFocusEffect(useCallback(() => { Updates.checkForUpdateAsync() .then(u => u.isAvailable ? Updates.fetchUpdateAsync().then(() => setReady(true)) : null) .catch(() => {}) // silence : l'OTA n'est jamais une erreur visible }, [])) {ready && ( <Card className="mb-4 flex-row items-center justify-between"> <AppText variant="caption">Mise à jour prête</AppText> <Button label="Redémarrer" variant="ghost" onPress={() => Updates.reloadAsync()} /> </Card> )}

Sur l’accueil uniquement (jamais /quiz — le focus effect y est absent par construction). Le catch silencieux : une vérification d’update qui échoue (offline…) ne doit JAMAIS déranger.

🧠 Quiz

1. Quel principe du ch. 1.2 rend l’OTA possible ?

Réponse

La séparation binaire natif / bundle JS : le bundle est un artefact remplaçable — EAS Update en livre un nouveau, le conteneur natif ne bouge pas.

2. Qu’est-ce qu’un channel, et où est-il fixé ?

Réponse

L’étiquette de distribution cuite dans le binaire au build (via le profil eas.json) : eas update --channel X sert tous les binaires branchés sur X. Nos deux : preview et production.

3. Que garantit la runtimeVersion ?

Réponse

Qu’un binaire ne reçoit que des bundles compatibles avec son natif : policy appVersion → tout changement natif s’accompagne d’un bump de version → les vieux binaires sont isolés des JS qui les crasheraient.

4. Quand une OTA publiée est-elle visible à l’écran ?

Réponse

Par défaut : téléchargée à une ouverture, appliquée au lancement suivant (d’où « fermer/ rouvrir deux fois » en test). Updates.reloadAsync() permet l’application immédiate — à proposer poliment.

5. Citez les trois canaux de mise à jour de Drill et leurs vitesses.

Réponse

Les questions (git push → GitHub raw : minutes), le JS (eas update : à la prochaine ouverture), le natif (rebuild + réinstallation : quand il faut). Chaque changement passe par le canal le plus léger possible.


👉 Chapitre suivant : 12.6 — Versionner & vivre avec — la routine de croisière : quel changement, quel canal, quelle version.