Capacitor アプリでネットワーク状態を検知するには、以下をインストールしてください @capacitor/network、呼び出し Network.getStatus() 現在の状態を取得し、 networkStatusChange 更新を購読します。これにより、デバイスが接続されているか、Wi-Fi かモバイル通信かを判断できます。インターネットが実際に使えるか、接続が従量制かどうかを確認するには、reachability チェックを追加してください。 @capgo/capacitor-network-diagnostics本ガイドでは、Capacitor 8 code を用いて、これら3つのレイヤーすべてを解説します。
「ユーザーはオンラインか?」を判断する3つの問い
| 質問 | API | 備考 |
|---|---|---|
| アクティブなネットワークインターフェースはありますか? | Network.getStatus().connected |
高速でイベント駆動型ですが、インターネット接続については何も示しません |
| どの種類のリンクですか? | connectionType |
wifi, cellular, none, unknown 公式プラグインで |
| インターネットは実際に動作していますか? | HTTPチェックまたは internetReachable |
リクエストまたはOS検証が必要 |
| 大容量のダウンロードは避けるべき? | expensive, constrained |
従量制ネットワークとiOSの低データモード |
オフライン関連のバグの多くは、1行目を3行目として扱ってしまうことから起こります。ログインページ付きのホテルWi-Fiは接続済みと報告しますが、 connected: true すべての API コールがタイムアウトしてしまいます。
Network pluginをインストールする
bun add @capacitor/network
bunx cap sync
設定は不要です。Androidでは、このプラグインが ACCESS_NETWORK_STATE マージされたmanifestに追加します。インストール時に付与される通常の権限です。iOSでは何も必要ありません。
現在のステータスを読み取る
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'
キャッシュから読み込むかネットワークから読み込むかを判断する前に、アプリ起動時にこれを呼び出してください。OSの状態を読み取るだけで通信は発生しないため、数ミリ秒で返されます。
ネットワークの変化を監視する
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();
}
実用上の2つのポイント:
- イベントをデバウンスする。 Wi-Fi からセルラーに切り替える際、両プラットフォームとも一時的に
none新しいリンクが確立する前に報告されることがあります。「オフライン」バナーを表示する前に 1〜2 秒のデバウンスを挟むことで、ちらつきを防げます。 - リスナーの削除 コンポーネントがアンマウントされた際にリスナーを削除しないと、ホットリロードやナビゲーション後にコールバックが複数回実行されてしまいます。
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;
}
オンラインへの切り替えは即座に反映され、オフラインへの切り替えは1.5秒待ちます。同じパターンは Vue の composable や Angular の service でも機能し、 BehaviorSubject.
「接続済み」がオンラインを意味しない理由
connected: true は OS のネットワークスタックから取得されます。次の場合でも true のままです:
- Wi-Fi ネットワークにキャプティブポータルがある場合(空港、ホテル、列車など)
- ルーターに上流接続がない場合
- DNSが壊れている、またはフィルタリングされている
- 企業のファイアウォールやVPNが API ドメインをブロックしている
- 自社バックエンドがダウンしている
解決策は、自分で管理している対象への到達性チェックです:
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);
}
}
アプリの起動時、 networkStatusChange レポートによると、 connected: trueリクエストが失敗した後にも実行してください。数秒おきのポーリングは避けてください。バッテリーとデータ通信を消耗します。ヘルスエンドポイントがCORSでアプリのオリジンを許可していることを確認してください(capacitor://localhost iOSでは適用され、 https://localhost Androidではデフォルトで有効)、または CapacitorHttp を使用してネイティブの code から送信します。
ネットワーク診断でより詳細なステータスを取得
@capgo/capacitor-network-diagnostics OSが把握している情報をより多く読み取り、WebViewのCORSをバイパスするネイティブチェックを実行できます:
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: { ... }
// }
フィールドとOSの対応関係:
| 項目 | Androidでは | iOSと |
|---|---|---|
internetReachable |
NET_CAPABILITY_VALIDATED (Androidがインターネットを確認) |
ミラーリング: NWPathMonitor パスのステータス |
expensive |
isActiveNetworkMetered() |
NWPath.isExpensive |
constrained |
常に false |
NWPath.isConstrained (低データモード) |
captivePortal |
NET_CAPABILITY_CAPTIVE_PORTAL |
未報告 |
vpn 型 |
はい | はい |
Androidの場合、 internetReachable は強力なシグナルです。OSがすでに接続をテスト済みのため、iOSではより近い状態を示します。 connectedなので、重要なフローではHTTPチェックを維持してください。
ネイティブの 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 });
ネイティブリクエストはCORSの対象外なので、サードパーティのホストもテストできます。
サポート用の「ネットワークレポート」画面
ユーザーが「アプリが動かない」と言ったとき、ワンタップの診断レポートが何時間ものやり取りを省きます。
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);
JSONをサポートチケットに添付するだけで、問題がDNSなのか、WebSocketのブロックなのか、キャプティブポータルなのか、回線が遅いのかがすぐにわかります。
従量制通信と低データモードへの対応
データ通信量を抑えることは、モバイルデータが高い市場でレビューを伸ばす近道です。
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;
}
動画の自動再生、画像の画質、バックグラウンド同期、大容量ダウンロードの判断に活用しましょう。AndroidではWi-Fiを従量制として設定できるため、Wi-Fiなら無料とは限らない点に注意が必要です。
オフラインバナーの実装
リクエストの失敗を見せるより、小さなバナーを出す方が分かりやすいです。上記のフックを使うと:
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" を使用して aria-live="polite" スクリーンリーダーが変更を通知できるようになります。フレームワーク別のUIについては、こちらのガイドを参照してください: Vue、Angular、Reactでオフライン画面を構築する方法.
オフライン中は処理をキューに積む
検知はオフライン対応の半分に過ぎません。ステータスがオフラインになったら:
- 読み取りはローカルストレージから行います。たとえば、次のようなSQLiteデータベースは
@capgo/capacitor-fast-sql大規模なデータセットを〜よりもうまく扱えますlocalStorage. - ユーザー操作は、タイムスタンプと冪等キーを付けてアウトボックステーブルに書き込みます。
- オンラインに
networkStatusChange復帰したら、次を実行します:canReachApi()、その後アウトボックスを順番にフラッシュします。 - 失敗したリクエストは、数分を上限とする指数バックオフでリトライします。
アプリを閉じても完了させる必要があるアップロードは、Webフォールバックではなくネイティブのアップローダーに渡してください。 fetch.
Webフォールバック
@capacitor/network ブラウザでも動作し、基になっているのは navigator.onLine および online と offline イベントです。つまり、code はその環境で動作し、 bun run dev PWAでも変更なしで動作しますが、 connectionType 多くの場合、 unknown Web上になります。Chromeの navigator.connection 公開し effectiveType と saveData、Safari は対応していないため、任意のヒントとして扱ってください。
トラブルシューティング
iOS シミュレーターではリスナーが発火しません:シミュレーターは Mac のネットワークを共有します。Mac の Wi-Fi を切り替えるか、機内モードを有効にしたデバイスでテストしてください。
connectionType が unknown VPN 接続時: 公式プラグインは次のものだけを報告します wifi, cellular, none と unknown. 次を返す diagnostics プラグインを使用してください vpn と ethernet.
ネットワークを切り替えると、アプリが1秒間オフライン表示になる: 上記のように、オフラインへの遷移にデバウンスを適用してください。
ヘルスチェックが実機では失敗するが、ブラウザでは成功する: CORS。許可する capacitor://localhost と https://localhost, または NetworkDiagnostics.testUrl, ネイティブで動作します。
navigator.onLine プラグインがオフラインと判定している間は true ですプラグインを信頼してください。一部の Android WebView は navigator.onLine 確実に更新されません。
オフライン修正をOTAで配信
オフライン処理は主に JavaScript で行います: しきい値、バナー、リトライロジック、キャッシュルール。 Capgo Live Updatesを使えば、それらをその日のうちに全ユーザー向けに調整できます。Capgo はバックグラウンドで更新バンドルをダウンロードし、失敗時は現在のバージョンを維持するので、接続が悪くても中途半端な更新状態にはなりません。 ネットワーク診断のドキュメント すべてのオプションを一覧表示します。