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の低データモード |
オフライン関連のバグの多くは、最初の行を3行目と誤認することで発生します。ログインページが必要なホテルのWi-Fiなどは、 connected: true 接続済みと報告される一方で、すべての API コールがタイムアウトしてしまいます。
Networkプラグインをインストール
bun add @capacitor/network
bunx cap sync
設定は不要です。Androidでは、プラグインが ACCESS_NETWORK_STATE をマニフェストに追加します。これはインストール時に許可される通常の権限です。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 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;
}
オンラインへの切り替えは即時反映し、オフラインへの切り替えは1.5秒待機します。同じパターンは、VueのcomposableやAngularのserviceでも次のように使えます。 BehaviorSubject.
なぜ「connected」がオンラインを意味しないのか
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チェックを併用してください。
ネイティブによる到達性チェック
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でのオフライン画面の構築方法.
オフライン中のタスクをキューイングする
検知はオフライン対応の半分に過ぎません。オフライン状態になったら:
- 読み取りはローカルストレージから行います。例えば、
@capgo/capacitor-fast-sqlのようなSQLiteデータベースを使えば、localStorage. - 大規模なデータセットも効率的に扱えます。ユーザーのアクションは、タイムスタンプと冪等性キー(idempotency key)と共にoutboxテーブルに書き込みます。
- オンラインに
networkStatusChange復帰した際に、以下を実行します:canReachApi()、その後、送信待ちキューを順番にフラッシュします。 - 失敗したリクエストは、最大数分に制限した指数バックオフで再試行します。
アプリを閉じても完了させる必要があるアップロードには、Web側ではなく fetch.
Webフォールバック
@capacitor/network もブラウザ上で動作し、これは〜に基づいています navigator.onLine および online と offline イベントです。つまり、code は bun run dev およびPWAでも変更なしで動作しますが、 connectionType 多くの場合、 unknown Web上では、 navigator.connection を公開しています effectiveType と saveData、Safariは対応していないため、あくまで補助的なヒントとして扱ってください。
トラブルシューティング
iOS Simulatorではリスナーが一度も発火しません:シミュレーターはMacのネットワークを共有するためです。MacのWi-Fiを切り替えるか、機内モードにした実機でテストしてください。
connectionType が unknown VPN接続時:公式プラグインは以下のみを報告します wifi, cellular, none と unknown。diagnosticsプラグインを使用してください。こちらでは以下が返されます vpn と ethernet.
ネットワーク切り替え時に、アプリが一瞬オフライン表示になる: 上記のように、オフラインへの遷移にデバウンスを適用します。
デバイスではヘルスチェックに失敗するが、ブラウザでは動作する場合: CORS。許可してください capacitor://localhost と https://localhost, または次を使用します NetworkDiagnostics.testUrl, これはネイティブで動作します。
navigator.onLine プラグインがオフラインと判定している間、true になる: プラグインを信頼してください。一部の Android WebView では更新が navigator.onLine 不安定な場合があります。
オフライン関連の修正を OTA で配信する
オフライン対応の大部分は JavaScript で行います(しきい値、バナー、リトライロジック、キャッシュルールなど)。 Capgo の Live Update、これらをユーザーごとに即座に調整できます。Capgo はバックグラウンドで update bundles をダウンロードし、ダウンロードに失敗した場合は現在のバージョンを維持するため、接続不良によってアップデートが中途半端な状態で残ることはありません。 ネットワーク診断ドキュメント すべてのオプションをリストしています。