Salta al contenuto principale

Come rilevare lo stato della rete in un'app Capacitor

Rileva lo stato della rete in un'app Capacitor: leggi lo stato online/offline, ascolta i cambiamenti, verifica l'accesso reale a Internet e gestisci le reti a consumo.

Come rilevare lo stato della rete (online/offline) in un'app mobile Capacitor

Per rilevare lo stato della rete in un'app Capacitor, installa @capacitor/network, chiama Network.getStatus() per lo stato attuale e iscriviti a networkStatusChange per gli aggiornamenti. Questo ti dice se il dispositivo è connesso e se usa Wi-Fi o rete cellulare. Per verificare se internet funziona davvero o se la connessione è a consumo, aggiungi un controllo di reachability o @capgo/capacitor-network-diagnostics. Questa guida copre tutti e tre i livelli con Capacitor 8 code.

Le tre domande dietro a “l'utente è online?”

Domanda API Note
C'è un'interfaccia di rete attiva? Network.getStatus().connected Veloce, event-driven, non dice nulla sull'accesso a internet
Che tipo di connessione è? connectionType wifi, cellular, none, unknown nel plugin ufficiale
Internet funziona davvero? Controllo HTTP o internetReachable Richiede una richiesta o una validazione OS
Dovrei evitare download pesanti? expensive, constrained Reti a consumo e iOS Low Data Mode

La maggior parte dei bug offline nasce dal trattare la prima riga come la terza. Un Wi-Fi in hotel con pagina di login segnala connected: true mentre ogni chiamata API va in timeout.

Installa il plugin Network

bun add @capacitor/network
bunx cap sync

Non serve configurazione. Su Android il plugin aggiunge ACCESS_NETWORK_STATE al manifest unificato, un normale permesso concesso all'installazione. iOS non richiede nulla.

Leggi lo stato attuale

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'

Chiamalo all'avvio dell'app prima di decidere se caricare dalla cache o dalla rete. Restituisce il valore in pochi millisecondi perché legge lo stato dell'OS, senza inviare traffico.

Ascolta i cambiamenti di rete

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();
}

Due dettagli pratici:

  • Applica il debounce all'evento. Passando dal Wi-Fi alla rete cellulare, entrambe le piattaforme possono segnalare brevemente none prima che la nuova connessione sia attiva. Un debounce di 1-2 secondi prima di mostrare un banner "offline" evita lo sfarfallio.
  • Rimuovi i listener quando un componente viene smontato, altrimenti il callback verrà eseguito più volte dopo hot reload e navigazione.

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;
}

Il passaggio online è immediato, quello offline attende 1,5 secondi. Lo stesso pattern funziona come composable Vue o servizio Angular con un BehaviorSubject.

Perché “connected” non significa online

connected: true proviene dallo stack di rete del sistema operativo. Rimane vero quando:

  • la rete Wi-Fi ha un captive portal (aeroporti, hotel, treni)
  • il router non ha una connessione upstream
  • DNS è rotto o filtrato
  • un firewall aziendale o una VPN blocca il tuo dominio API
  • il tuo backend è offline

La soluzione è un check di reachability contro qualcosa che controlli tu:

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);
  }
}

Eseguilo all'avvio dell'app, quando networkStatusChange report connected: true, e dopo un fallimento della richiesta. Non fare polling ogni pochi secondi: consuma batteria e dati. Assicurati che l'endpoint di health consenta l'origine della tua app in CORS (capacitor://localhost su iOS, https://localhost su Android in modo predefinito), oppure usa CapacitorHttp per inviarlo da code nativo.

Ottieni uno stato più dettagliato con la diagnostica di rete

@capgo/capacitor-network-diagnostics legge più informazioni note al sistema operativo e può eseguire check nativi che bypassano il CORS di 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: { ... }
// }

Come i campi si mappano sul sistema operativo:

Campo Android iOS
internetReachable NET_CAPABILITY_VALIDATED (Android ha sondato Internet) Specchia il NWPathMonitor stato del percorso
expensive isActiveNetworkMetered() NWPath.isExpensive
constrained Sempre false NWPath.isConstrained (Modalità dati ridotti)
captivePortal NET_CAPABILITY_CAPTIVE_PORTAL Non riportato
vpn Tipo Sì Sì

Su Android, internetReachable è un segnale forte perché l'OS ha già testato la connessione. Su iOS è più simile a connected, quindi mantieni il controllo HTTP per i flussi critici.

Controlli nativi di reachability

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 });

Le richieste native non sono soggette a CORS, quindi puoi testare anche host di terze parti.

Una schermata “network report” per il supporto

Quando gli utenti dicono "l'app non funziona", un report diagnostico con un solo tap ti fa risparmiare ore di botta e risposta:

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);

Allega il JSON a un ticket di supporto. Capirai subito se il problema è il DNS, un WebSocket bloccato, un captive portal o una connessione lenta.

Adattati alle connessioni a consumo e a Low Data Mode

Rispettare i limiti dei dati è una mossa vincente per le recensioni nei mercati con dati mobili costosi:

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;
}

Use it to decide on video autoplay, image quality, background sync, and large downloads. On Android, users can mark a Wi-Fi network as metered, so do not assume Wi-Fi is free.

Crea un banner offline

Un piccolo banner è più chiaro di richieste fallite. Usando l'hook qui sopra:

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" con aria-live="polite" permette agli screen reader di annunciare il cambiamento. Per UI specifiche del framework, vedi la nostra guida su come creare una schermata offline in Vue, Angular e React.

Mettere in coda i task offline

Il rilevamento è solo metà del supporto offline. Quando lo stato passa a offline:

  1. Serve i dati dallo storage locale. Un database SQLite come @capgo/capacitor-fast-sql gestisce dataset ampi meglio di localStorage.
  2. Scrivi le azioni dell'utente in una tabella outbox con timestamp e una chiave di idempotenza.
  3. Su networkStatusChange connessi, esegui canReachApi(), quindi elabora la coda in ordine.
  4. Riprova le richieste fallite con backoff esponenziale, limitato a pochi minuti.

Per gli upload che devono sopravvivere alla chiusura dell'app, affidali a un uploader nativo anziché fetch.

Fallback web

@capacitor/network funziona anche nel browser, dove si basa su navigator.onLine e il online e offline events. Il che significa che il tuo code funziona in bun run dev e in una PWA senza modifiche, ma connectionType sarà spesso unknown sulla web. Chrome navigator.connection exposes effectiveType e saveData, Safari non lo fa, quindi considerali come suggerimenti opzionali.

Risoluzione problemi

Il listener non viene mai attivato sull'iOS Simulator: il simulatore condivide la rete del Mac. Attiva/disattiva il Wi-Fi sul Mac o prova su un dispositivo in modalità aereo.

connectionType è unknown su una VPN: il plugin ufficiale riporta solo wifi, cellular, none e unknown. Usa il plugin diagnostics, che restituisce vpn e ethernet.

L'app risulta offline per un secondo durante il cambio di rete: debounce la transizione offline come mostrato sopra.

Il health check fallisce sul dispositivo ma funziona nel browser: CORS. Consenti capacitor://localhost e https://localhost, oppure usa NetworkDiagnostics.testUrl, che viene eseguito in modo nativo.

navigator.onLine è true mentre il plugin segnala offline: fidati del plugin. Alcuni WebView Android non aggiornano navigator.onLine reliably.

Distribuisci fix offline via OTA

La gestione offline è per lo più JavaScript: soglie, banner, logica di retry, regole di cache. Con Live updates di Capgo, puoi regolare tutto per ogni utente lo stesso giorno. Capgo scarica i bundle di aggiornamento in background e mantiene la versione corrente se il download fallisce, così una connessione instabile non lascia mai gli utenti con un aggiornamento parzialmente installato. I documenti sulla diagnostica di rete elencano tutte le opzioni.

Live updates per le app Capacitor

Quando un bug nel web-layer è live, rilascia la fix tramite Capgo invece di aspettare giorni l'approvazione degli store. Gli utenti ricevono l'update in background, mentre le modifiche native restano nel normale processo di review.

Ultimi articoli dal blog

Capgo ti offre gli insight migliori che ti servono per creare un'app mobile davvero professionale.