Zum Hauptinhalt springen

So prüfst du den Netzwerkstatus in einer Capacitor App

Netzwerkstatus in einer Capacitor App erkennen: Online-/Offline-Status auslesen, auf Änderungen reagieren, echten Internetzugriff prüfen und mit begrenztem Datenvolumen umgehen.

Online- und Offline-Netzwerkstatus in einer mobilen Capacitor-App erkennen

Um den Netzwerkstatus in einer Capacitor-App zu erkennen, installiere @capacitor/network, rufe Network.getStatus() für den aktuellen Status und abonnieren Sie networkStatusChange für Updates. Das zeigt Ihnen, ob das Gerät verbunden ist und ob es sich um Wi-Fi oder Mobilfunk handelt. Um zu prüfen, ob das Internet tatsächlich funktioniert oder ob die Verbindung getaktet ist, fügen Sie einen Reachability-Check oder @capgo/capacitor-network-diagnosticshinzu. Dieser Guide deckt alle drei Ebenen mit Capacitor 8 code ab.

Die drei Fragen hinter „Ist der User online?“

Frage API Notizen
Gibt es eine aktive Netzwerkschnittstelle? Network.getStatus().connected Schnell, eventgesteuert, sagt nichts über den Internetzugriff aus
Um welche Art von Verbindung handelt es sich? connectionType wifi, cellular, none, unknown im offiziellen Plugin
Funktioniert das Internet tatsächlich? HTTP-Check oder internetReachable Benötigt eine Anfrage oder OS-Validierung
Große Downloads vermeiden? expensive, constrained Gebührenpflichtige Netzwerke und iOS Low Data Mode

Die meisten Offline-Bugs entstehen, wenn man die erste Zeile wie die dritte behandelt. Ein Hotel-WLAN mit Login-Seite meldet connected: true während jeder API-Aufruf in einem Timeout endet.

Network-Plugin installieren

bun add @capacitor/network
bunx cap sync

Keine Konfiguration erforderlich. Auf Android fügt das Plugin ACCESS_NETWORK_STATE zum gemergten Manifest hinzu – eine normale Berechtigung, die bei der Installation erteilt wird. iOS benötigt nichts.

Aktuellen Status auslesen

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'

Rufe es beim App-Start auf, bevor du entscheidest, ob du aus dem Cache oder dem Netzwerk lädst. Es liefert in wenigen Millisekunden ein Ergebnis, da es den OS-Zustand liest und keinen Traffic sendet.

Auf Netzwerkänderungen lauschen

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

Zwei praktische Details:

  • Das Event debouncen. Beim Wechsel von Wi-Fi zu Mobilfunk können beide Plattformen kurzzeitig melden, none bevor die neue Verbindung steht. Ein Debounce von 1 bis 2 Sekunden, bevor ein „Offline“-Banner erscheint, verhindert Flackern.
  • Listener entfernen wenn eine Komponente unmountet wird, sonst läuft der Callback nach Hot Reloads und Navigation mehrfach.

React Hook

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

Der Online-Status wird sofort angewendet, der Offline-Status wartet 1,5 Sekunden. Das gleiche Pattern funktioniert als Vue Composable oder Angular Service mit einem BehaviorSubject.

Warum „connected“ nicht „online“ bedeutet

connected: true kommt aus dem Netzwerk-Stack des OS. Er bleibt true, wenn:

  • das Wi-Fi-Netz ein Captive Portal hat (Flughäfen, Hotels, Züge)
  • der Router keine Upstream-Verbindung hat
  • DNS ist gestört oder gefiltert
  • eine Firmen-Firewall oder ein VPN blockiert deine API Domain
  • dein eigenes Backend ist offline

Die Lösung ist ein Reachability-Check gegen etwas, das du kontrollierst:

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

Führe ihn beim App-Start aus, wenn networkStatusChange meldet connected: true, und nachdem ein Request fehlschlägt. Nutze kein Polling alle paar Sekunden, das verbraucht Akku und Daten. Stelle sicher, dass der Health-Endpoint den Origin deiner App in CORS erlaubt (capacitor://localhost unter iOS, https://localhost unter Android standardmäßig), oder nutze CapacitorHttp um es über natives code zu senden.

Erhalte detailliertere Statusinfos mit Netzwerk-Diagnose

@capgo/capacitor-network-diagnostics liest mehr aus dem OS aus und kann native Checks ausführen, die WebView CORS umgehen:

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: { ... }
// }

So werden die Felder auf das OS abgebildet:

Feld Android iOS
internetReachable NET_CAPABILITY_VALIDATED (Android hat das Internet geprüft) Spiegelt den NWPathMonitor Pfadstatus
expensive isActiveNetworkMetered() NWPath.isExpensive
constrained Immer false NWPath.isConstrained (Modus mit geringem Datenvolumen)
captivePortal NET_CAPABILITY_CAPTIVE_PORTAL Keine Angabe
vpn type Ja Ja

Unter Android, internetReachable ist ein starkes Signal, da das OS die Verbindung bereits getestet hat. Auf iOS ist es näher an connected, behalte also den HTTP-Check für kritische Flows bei.

Native Erreichbarkeitsprüfungen

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

Native Requests unterliegen nicht CORS, sodass du auch Drittanbieter-Hosts testen kannst.

Ein Support-„Netzwerkbericht“-Screen

Wenn Nutzer sagen „Die App funktioniert nicht“, spart ein Diagnosebericht mit einem Tap Stunden an Hin- und Hergerede:

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

Häng das JSON an ein Support-Ticket an. Du siehst sofort, ob das Problem DNS ist, ein blockierter WebSocket, ein Captive Portal oder eine langsame Verbindung.

Anpassung an gebührenpflichtige Verbindungen und Low Data Mode

Datengrenzen zu respektieren ist ein einfacher Hebel für bessere Bewertungen in Märkten mit teuren mobilen Daten:

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

Nutze es, um über Video-Autoplay, Bildqualität, Hintergrund-Sync und große Downloads zu entscheiden. Auf Android können Nutzer ein Wi-Fi-Netzwerk als gebührenpflichtig markieren, geh also nicht davon aus, dass Wi-Fi kostenlos ist.

Offline-Banner erstellen

Ein kleines Banner ist klarer als fehlgeschlagene Requests. Mit dem Hook oben:

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" mit aria-live="polite" lässt Screenreader die Änderung ansagen. Für framework-spezifische UI siehe unseren Guide zum Bauen eines Offline-Screens in Vue, Angular und React.

Arbeit offline in die Warteschlange stellen

Erkennung ist nur die halbe Miete beim Offline-Support. Wenn der Status auf offline wechselt:

  1. Liest aus dem lokalen Speicher. Eine SQLite-Datenbank wie @capgo/capacitor-fast-sql kommt mit großen Datensätzen besser zurecht als localStorage.
  2. Schreibe Nutzeraktionen in eine Outbox-Tabelle mit Zeitstempel und Idempotenzschlüssel.
  3. An networkStatusChange verbunden, führe aus canReachApi(), dann die Outbox der Reihe nach leeren.
  4. Wiederhole fehlgeschlagene Anfragen mit exponentiellem Backoff, begrenzt auf wenige Minuten.

Für Uploads, die das Schließen der App überleben müssen, übergib sie an einen nativen Uploader statt an fetch.

Web-Fallback

@capacitor/network läuft auch im Browser und basiert dort auf navigator.onLine und online und offline events. Das bedeutet, dass dein code in bun run dev und in einer PWA ohne Änderungen, aber connectionType wird im Web oft unknown sein. Chrome’s navigator.connection stellt effectiveType und saveData, Safari tut dies nicht; behandle sie also als optionale Hinweise.

Fehlerbehebung

Der Listener wird im iOS Simulator nie ausgelöst: Der Simulator nutzt das Netzwerk des Macs. Schalte das WLAN am Mac um oder teste auf einem Gerät im Flugmodus.

connectionType ist unknown über ein VPN: das offizielle Plugin meldet nur wifi, cellular, none und unknown. Nutze das Diagnostics-Plugin, das zurückgibt vpn und ethernet.

Die App zeigt beim Netzwerkwechsel kurzzeitig „offline“ an: den Offline-Übergang wie oben gezeigt debouncen.

Der Health Check schlägt auf dem Gerät fehl, funktioniert aber im Browser: CORS. Erlauben capacitor://localhost und https://localhost, oder nutze NetworkDiagnostics.testUrl, das nativ läuft.

navigator.onLine ist true, während das Plugin offline meldet: vertrauen Sie dem Plugin. Einige Android WebViews aktualisieren nicht navigator.onLine zuverlässig.

Offline-Fixes via Over-the-Air ausrollen

Offline-Handling ist meist JavaScript: Thresholds, Banner, Retry-Logik, Cache-Regeln. Mit Capgo Live Updates, kannst du diese noch am selben Tag für jeden Nutzer anpassen. Capgo lädt Update-Bundles im Hintergrund herunter und behält die aktuelle Version, falls ein Download fehlschlägt. So bleibt niemand bei schlechter Verbindung mit einem halb installierten Update zurück. Die Netzwerkdiagnose-Docs listet jede Option auf.

Live-Updates für Capacitor-Apps

Wenn ein Bug im Web-Layer live ist, liefere den Fix über Capgo aus, statt Tage auf die App-Store-Freigabe zu warten. Nutzer bekommen das Update im Hintergrund, während native Änderungen im normalen Review-Prozess bleiben.

Neues aus unserem Blog

Capgo gibt dir die besten Insights, um eine wirklich professionelle Mobile App zu erstellen.