Aller au contenu principal

Comment détecter l'état du réseau dans une application Capacitor

Détectez l'état du réseau dans une application Capacitor : lisez l'état online/offline, écoutez les changements, vérifiez l'accès réel à Internet et gérez les réseaux limités.

Détecter l’état du réseau en ligne et hors ligne dans une application mobile Capacitor

Pour détecter l’état du réseau dans une application Capacitor, installez @capacitor/network, appelez Network.getStatus() pour l’état actuel, et abonnez-vous à networkStatusChange pour les mises à jour. Cela vous indique si l’appareil est connecté et s’il passe par le Wi-Fi ou les données mobiles. Pour savoir si Internet fonctionne réellement, ou si la connexion est facturée au volume, ajoutez une vérification de reachability ou @capgo/capacitor-network-diagnostics. Ce guide couvre les trois niveaux avec Capacitor 8 code.

Les trois questions derrière « l’utilisateur est-il en ligne ? »

Question API Notes
Y a-t-il une interface réseau active ? Network.getStatus().connected Rapide, piloté par événements, ne dit rien sur l’accès à Internet
De quel type de liaison s’agit-il ? connectionType wifi, cellular, none, unknown dans le plugin officiel
Internet fonctionne-t-il vraiment ? Vérification HTTP ou internetReachable Nécessite une requête ou une validation du système d’exploitation
Faut-il éviter les gros téléchargements ? expensive, constrained Réseaux facturés et mode Données réduites sur iOS

La plupart des bugs hors ligne viennent du fait de traiter la première ligne comme la troisième. Un Wi-Fi d’hôtel avec une page de connexion renvoie connected: true tandis que chaque appel API expirait.

Installer le plugin Network

bun add @capacitor/network
bunx cap sync

Aucune configuration n’est nécessaire. Sur Android, le plugin ajoute ACCESS_NETWORK_STATE au manifeste fusionné, une permission normale accordée à l’installation. iOS ne nécessite rien.

Lire l’état actuel

import { Network } from '@capacitor/network';

const status = await Network.getStatus();
console.log(status.connected);      // true or false
console.log(status.connectionType); // 'wifi' | 'cellular' | 'none' | 'unknown'

Appelez-l’au démarrage de l’app avant de décider de charger depuis le cache ou le réseau. Il répond en quelques millisecondes car il lit l’état du système d’exploitation, sans envoyer de trafic.

Écouter les changements de réseau

import { Network, type ConnectionStatus } from '@capacitor/network';
import type { PluginListenerHandle } from '@capacitor/core';

let handle: PluginListenerHandle | undefined;

export async function watchNetwork(onChange: (s: ConnectionStatus) => void) {
  onChange(await Network.getStatus());
  handle = await Network.addListener('networkStatusChange', onChange);
}

export async function stopWatchingNetwork() {
  await handle?.remove();
}

Deux détails pratiques :

  • Appliquez un debounce à l’événement. Lors du passage du Wi-Fi au réseau mobile, les deux plateformes peuvent brièvement signaler none avant que le nouveau lien ne soit actif. Un debounce de 1 à 2 secondes avant d’afficher un bandeau « hors ligne » évite les clignotements.
  • Retirez les listeners lors du démontage d’un composant, sinon le callback s’exécutera plusieurs fois après les hot reloads et les navigations.

Hook React

import { useEffect, useState } from 'react';
import { Network, type ConnectionStatus } from '@capacitor/network';

export function useNetworkStatus() {
  const [status, setStatus] = useState<ConnectionStatus>({ connected: true, connectionType: 'unknown' });

  useEffect(() => {
    let timer: ReturnType<typeof setTimeout>;
    const listener = Network.addListener('networkStatusChange', (s) => {
      clearTimeout(timer);
      timer = setTimeout(() => setStatus(s), s.connected ? 0 : 1500);
    });
    Network.getStatus().then(setStatus);
    return () => {
      clearTimeout(timer);
      listener.then((l) => l.remove());
    };
  }, []);

  return status;
}

Le passage en ligne s’applique immédiatement, le passage hors ligne attend 1,5 seconde. Le même schéma fonctionne comme un composable Vue ou un service Angular avec un BehaviorSubject.

Pourquoi « connecté » ne signifie pas en ligne

connected: true comes from the OS network stack. It stays true when:

  • le réseau Wi-Fi a un portail captif (aéroports, hôtels, trains)
  • le routeur n’a pas de connexion en amont
  • le DNS est cassé ou filtré
  • un pare-feu d’entreprise ou un VPN bloque votre domaine API
  • votre propre backend est en panne

La solution est une vérification de joignabilité d’une ressource que vous contrôlez :

export async function canReachApi(timeoutMs = 4000): Promise<boolean> {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);
  try {
    const res = await fetch('https://api.example.com/health', {
      method: 'HEAD',
      cache: 'no-store',
      signal: controller.signal,
    });
    return res.ok;
  } catch {
    return false;
  } finally {
    clearTimeout(timer);
  }
}

Lancez-l’au démarrage de l’app, lorsque networkStatusChange signale connected: true, et après l’échec d’une requête. Ne faites pas de polling toutes les quelques secondes : cela vide la batterie et consomme des données. Vérifiez que l’endpoint de santé autorise l’origine de votre app dans CORS (capacitor://localhost sur iOS, https://localhost sur Android par défaut), ou utilisez CapacitorHttp pour l’envoyer depuis le natif code.

Obtenir un statut plus précis avec les diagnostics réseau

@capgo/capacitor-network-diagnostics lit davantage de ce que l’OS sait et peut exécuter des vérifications natives qui contournent le CORS de la WebView :

bun add @capgo/capacitor-network-diagnostics
bunx cap sync
import { NetworkDiagnostics } from '@capgo/capacitor-network-diagnostics';

const status = await NetworkDiagnostics.getNetworkStatus();
// {
//   connected: true,
//   connectionType: 'wifi' | 'cellular' | 'ethernet' | 'vpn' | 'other' | 'unknown' | 'none',
//   internetReachable: true,
//   expensive: false,      // metered / cellular
//   constrained: false,    // iOS Low Data Mode
//   captivePortal: false,  // Android only
//   details: { ... }
// }

Comment les champs correspondent à l’OS :

Champ Android iOS
internetReachable NET_CAPABILITY_VALIDATED (Android a testé l’accès Internet) Reflète le NWPathMonitor statut du chemin
expensive isActiveNetworkMetered() NWPath.isExpensive
constrained Toujours false NWPath.isConstrained (Mode données réduites)
captivePortal NET_CAPABILITY_CAPTIVE_PORTAL Non communiqué
vpn type Oui Oui

Sur Android, internetReachable est un signal fiable car le système a déjà testé la connexion. Sur iOS, il est plus proche de connected, conservez donc la vérification HTTP pour les flux critiques.

Vérifications de connectivité natives

const api = await NetworkDiagnostics.testUrl({
  url: 'https://api.example.com/health',
  method: 'HEAD',
  timeoutMs: 4000,
});
// api.reachable, api.statusCode, api.durationMs, api.errorCode

const socket = await NetworkDiagnostics.testWebSocket({ url: 'wss://realtime.example.com' });
const port = await NetworkDiagnostics.testPort({ host: 'mqtt.example.com', port: 8883 });

Les requêtes natives ne sont pas soumises à CORS, vous pouvez donc tester des hôtes tiers.

Un écran de « rapport réseau » pour le support

Quand les utilisateurs disent « l’app ne marche pas », un rapport de diagnostic en un clic fait gagner des heures d’allers-retours :

const report = await NetworkDiagnostics.runDiagnostics({
  urls: [{ url: 'https://api.example.com/health' }, { url: 'https://cdn.example.com/ping' }],
  websockets: [{ url: 'wss://realtime.example.com' }],
  download: { url: 'https://cdn.example.com/1mb.bin', maxBytes: 1_048_576 },
  packetLoss: { url: 'https://api.example.com/health', count: 5 },
});

console.log(report.issues);        // human-readable list of problems
console.log(report.download?.mbps);
console.log(report.packetLoss?.lossPercent);

Joignez le JSON à un ticket de support. Vous verrez immédiatement si le problème vient du DNS, d’un WebSocket bloqué, d’un portail captif ou d’une connexion lente.

S'adapter aux connexions limitées et au mode Économiseur de données

Respecter les limites de données est un gain rapide pour les avis dans les marchés où les données mobiles coûtent cher :

export async function shouldPrefetchMedia() {
  const s = await NetworkDiagnostics.getNetworkStatus();
  if (!s.connected) return false;
  if (s.constrained) return false; // user asked iOS to save data
  if (s.expensive) return false;   // cellular or metered Wi-Fi
  return true;
}

Utilisez-la pour décider de l'autoplay vidéo, de la qualité des images, de la synchronisation en arrière-plan et des gros téléchargements. Sur Android, les utilisateurs peuvent marquer un réseau Wi-Fi comme facturé, ne supposez donc pas que le Wi-Fi est gratuit.

Créer une bannière hors ligne

Une petite bannière est plus claire que des requêtes qui échouent. Avec le hook ci-dessus :

export function OfflineBanner() {
  const { connected } = useNetworkStatus();
  if (connected) return null;
  return (
    <div role="status" aria-live="polite" className="offline-banner">
      You are offline. Changes will sync when you reconnect.
    </div>
  );
}

role="status" par aria-live="polite" permet aux lecteurs d’écran d’annoncer le changement. Pour l’interface spécifique à un framework, consultez notre guide la création d’un écran hors ligne en Vue, Angular et React.

Mettre les tâches en file d'attente hors ligne

La détection ne représente que la moitié du support hors ligne. Lorsque le statut passe en hors ligne :

  1. Gérez les lectures depuis le stockage local. Une base de données SQLite telle que @capgo/capacitor-fast-sql gère mieux les grands jeux de données que localStorage.
  2. Écrire les actions utilisateur dans une table outbox avec un horodatage et une clé d’idempotence.
  3. Activé networkStatusChange à la connexion, exécuter canReachApi(), puis vider la table outbox dans l’ordre.
  4. Réessayer les requêtes échouées avec un backoff exponentiel, plafonné à quelques minutes.

Pour les uploads qui doivent survivre à la fermeture de l’app, confiez-les à un uploader natif plutôt qu’à fetch.

Fallback web

@capacitor/network fonctionne également dans le navigateur, où il est basé sur navigator.onLine et le online et offline events. Cela signifie que votre code fonctionne dans bun run dev et dans une PWA sans modification, mais connectionType sera souvent unknown sur le web. Chrome navigator.connection expose effectiveType et saveData, Safari non, donc considérez-les comme des indices optionnels.

Troubleshooting

Le listener ne se déclenche jamais sur le simulateur iOS: le simulateur partage le réseau du Mac. Activez le Wi-Fi sur le Mac ou testez sur un appareil en mode avion.

connectionType est unknown sur un VPN: le plugin officiel ne signale que wifi, cellular, none et unknown. Utilisez le plugin de diagnostic, qui renvoie vpn et ethernet.

L’application affiche « hors ligne » pendant une seconde lors du changement de réseau: debounce the offline transition as shown above.

Le health check échoue sur l'appareil mais fonctionne dans le navigateur: CORS. Autoriser capacitor://localhost et https://localhost, ou utilisez NetworkDiagnostics.testUrl, qui s'exécute nativement.

navigator.onLine est vrai alors que le plugin indique hors ligne: faites confiance au plugin. Certaines WebViews Android ne se mettent pas à jour navigator.onLine reliably.

Déployez des correctifs hors ligne en OTA

La gestion du mode hors ligne est surtout du JavaScript : seuils, bannières, logique de retry, règles de cache. Avec les live updates Capgo, vous pouvez les ajuster pour chaque utilisateur le jour même. Capgo télécharge les bundles de mise à jour en arrière-plan et conserve la version actuelle si le téléchargement échoue, ainsi une mauvaise connexion ne laisse jamais les utilisateurs avec une mise à jour à moitié installée. Les docs de diagnostic réseau liste toutes les options.

Live updates pour les apps Capacitor

Lorsqu'un bug de la couche web est en ligne, déployez le correctif via Capgo au lieu d'attendre des jours l'approbation des stores. Les utilisateurs reçoivent la mise à jour en arrière-plan, tandis que les changements natifs suivent le circuit de validation habituel.

Les derniers articles du blog

Capgo vous offre les meilleures informations pour créer une application mobile vraiment professionnelle.