Jean Desauw
Retour au Blog
React Native
BLE
Mobile Engineering
Bluetooth

React Native BLE en production : ce qu'on ne te dit pas

JD
Jean Desauw
4 min de lecture
React Native BLE en production : ce qu'on ne te dit pas

Monte une connexion BLE en React Native et ça marche en 10 minutes. Ensuite tu testes sur un Android de QA. Le scan ne renvoie rien. Sur iOS, la connexion tombe quand l'app passe en arrière-plan et ne revient jamais. Tu découvres que le GPS doit être activé sur Android juste pour découvrir les appareils à proximité.

Personne ne t'avait prévenu.

Ça fait plus d'un an que je travaille avec react-native-ble-manager sur une app en production. Les setups BLE en React Native ont l'air simples dans la doc. Ils ne le sont plus une fois que tu sors d'un appareil de dev et d'un environnement contrôlé.

Le changement de permissions Android qui piège tout le monde

La surprise en production la plus courante en ce moment : ton app marche sur Android 11, casse sur Android 12.

Avant Android 12, le scan BLE exigeait ACCESS_FINE_LOCATION. Sans raison conceptuelle. C'était comme ça. À partir d'Android 12, Google a divisé les permissions. Il te faut BLUETOOTH_SCAN et BLUETOOTH_CONNECT comme runtime dangerous permissions, les deux.

Le piège : si tu zappes le flag neverForLocation: true dans tes options de scan, Android 12 exige quand même la permission de localisation en plus. La plupart des apps qui n'ont pas touché à leur code BLE depuis un an s'y cassent les dents.

await BleManager.scan(serviceUUIDs, 5, true, {
  neverForLocation: true,
});

Un flag, un blast radius important.

Pour Android 6 jusqu'à 11 : la permission de localisation ET le GPS doivent être activés au niveau de l'OS. Pas juste accordée. Le GPS activé. Ça casse dès qu'un utilisateur a désactivé la localisation pour économiser la batterie. Vérifie l'état du GPS avant de scanner. Ne suppose pas qu'il est actif parce que la permission a été accordée il y a des mois.

État de connexion BLE en React Native : le mythe de la reconnexion

Ni iOS ni Android ne se reconnecte automatiquement après une liaison tombée. Tu reçois un event de déconnexion. Puis plus rien. Monte la reconnexion toi-même.

BleManager.addListener(
  'BleManagerDisconnectPeripheral',
  ({ peripheral }) => scheduleReconnect(peripheral)
);
 
function scheduleReconnect(id: string, attempt = 0) {
  const delay = Math.min(1000 * 2 ** attempt, 30_000);
  setTimeout(async () => {
    try {
      await BleManager.connect(id);
    } catch {
      scheduleReconnect(id, attempt + 1);
    }
  }, delay);
}

Il y a aussi un failure mode pire que les déconnexions : les reconnexions qui réussissent en apparence mais pas réellement. connect() resolve. Puis discoverAllServicesAndCharacteristics() reste bloqué pour toujours. L'appareil a l'air connecté. Il ne l'est pas.

Le fix : full teardown à chaque reconnexion. Déconnecte explicitement d'abord, même si tu penses que la liaison est déjà morte. Ensuite reconnecte from scratch. Ne fais pas confiance à l'état de connexion seul.

iOS et Android : plus différents que ne le suggère la doc

iOS ne met jamais en timeout BleManager.connect(). Si le peripheral est éteint ou hors de portée, l'appel attend indéfiniment. Pas de timeout intégré. Ton UI reste figée sans feedback jusqu'à ce que tu en ajoutes un.

const connectWithTimeout = (id: string, ms = 10_000): Promise<void> =>
  Promise.race([
    BleManager.connect(id),
    new Promise<never>((_, reject) =>
      setTimeout(() => reject(new Error('BLE connection timeout')), ms)
    ),
  ]);

Le scan en arrière-plan sur Android exige un foreground service avec une notification persistante à partir d'Android 8. Si ton app companion doit découvrir des appareils en arrière-plan, prévois le setup du foreground service. Ce n'est pas optionnel.

Le filtrage du scan est aussi spécifique à chaque plateforme. Passer des serviceUUIDs sur iOS restreint le scan aux peripherals qui annoncent ce service. Sur Android, le même paramètre est silencieusement ignoré. Tu récupères tous les appareils BLE à proximité indépendamment. Dans un environnement chargé, filtre côté app pour Android sinon tu remonteras des appareils non pertinents.

Je couvre comment structurer des codebases pour que les agents s'y retrouvent dans le cours d'agentic coding.

Ce qui marche vraiment en production

Le comportement BLE n'est pas standardisé d'un hardware à l'autre. Des chipsets différents gèrent le timing, la négociation MTU et la reconnexion différemment. Le même code se comporte différemment sur un Samsung milieu de gamme, un Pixel et un appareil MediaTek d'entrée de gamme. Teste sur au moins trois appareils Android avant de livrer.

Ne teste pas sur un émulateur. Le BLE ne marche dans aucun émulateur, iOS ou Android. Tu vas perdre du temps à diagnostiquer des problèmes qui n'existent pas sur du vrai hardware.

Pour le mode arrière-plan iOS : si tu dois garder une connexion vivante pendant que l'app est en arrière-plan, déclare bluetooth-central dans les background modes du Info.plist. Sans ça, iOS suspend l'app en quelques secondes après le passage en arrière-plan. Avec ça, tu obtiens la state restoration. iOS peut même relancer ton app quand un peripheral connu se connecte.

La fenêtre d'exécution en arrière-plan de 30 secondes s'applique toujours. Pour les transferts en masse, découpe les données autour de cette contrainte.

L'architecture qui a tenu : un seul context provider pour tout l'état BLE. Chaque appel BleManager.addListener vit là. Les composants s'abonnent à l'état et dispatchent des actions. Ils ne touchent jamais BleManager directement.

const BLEContext = createContext<BLEContextValue>(null!);
 
export function BLEProvider({ children }: { children: React.ReactNode }) {
  const [peripherals, setPeripherals] = useState(new Map<string, Peripheral>());
  // All BleManager listeners centralized here.
  // Components see state, not the library.
}

Quand une reconnexion se produit en arrière-plan, le composant n'en sait rien. Il voit juste la connexion revenir. Le provider gère la complexité de façon invisible.

Le BLE en React Native n'est pas algorithmiquement difficile. Il est difficile parce que les plateformes ne sont pas d'accord sur la plupart des détails, et la doc zappe complètement les edge cases de production.

Je couvre la structure de codebase pour les agents et le passage à l'échelle des workflows dans le cours d'agentic coding.

Premier chapitre gratuit

Apprenez le workflow agentic coding que j'utilise en production

Comment je structure mes repos, gère le contexte, et fais tourner des agents en production. Écrit pour que vous puissiez faire pareil.