【Flutter】runZonedGuarded【よく理解せずに使っていたものたち】
Flutterアプリ開発でよく目にする以下のコード:
Future<void> main() async {
// ↓↓↓これ↓↓↓
await runZonedGuarded(() async {
// アプリを起動
// 省略...
runApp(const MyApp());
}, (error, stackTrace) {
// エラーハンドリング
// 省略...
});
}
runZonedGuarded
の仕組みや目的を深掘りせずに「テンプレート」として使ってしまっていましたが一体何をしているのでしょうか。この記事では自分なりに調べて咀嚼した内容をまとめています。
runZonedGuarded
とは?
runZonedGuarded
はDartのdart:async
ライブラリに属する関数です。
意図的にゾーン (Zone) を作成することで、Dartの非同期処理で発生する未処理のエラーを効率的にハンドリングするために使用されます。
提供する機能について、公式ドキュメントから一部抜粋・意訳します。
-
body
を新しいエラーゾーンで実行。 - ゾーン内で発生した同期・非同期エラーをキャッチして
onError
ハンドラで処理。 - 非同期エラーが別のゾーンに伝播しないように設計。
役割
漏れる非同期エラーのキャッチ
非同期処理のエラーはtry-catch
を抜けた後に発生する可能性があるため、通常のエラーハンドリングではキャッチできません。
例えば、HTTPリクエスト中に未処理の例外が発生した場合、アプリがクラッシュする可能性があります。
このようなエラーをrunZonedGuarded
でキャッチして適切に処理することで、アプリの安定性を保つことができます。
ログ収集との連携
runZonedGuarded
を使うことで、エラーが指定したゾーン内で集中的に処理されるため、エラーの収集やログ出力が一箇所で行えます。FirebaseCrashlyticsなどのエラーログ収集ツールと組み合わせることで、未処理エラーを記録してデバッグやモニタリングに役立てられます。
(補足)ゾーンとは?
先ほどから何度も登場している「ゾーン」とは、一体何でしょうか。
ゾーンとは、言葉のイメージの通り、特別な部屋のようなものです。特定のエリアを囲うことでゾーンを作成し、中で起きたこと(エラーや動作)はそのエリアのルールに従って管理できるのです。
具体例
話を戻して、以下のコードでrunZonedGuarded
の挙動を確認します:
void main() {
runZonedGuarded(() {
// ゾーンAで Future を作成
final future = Future.error(Exception('ゾーンAで発生したエラー'));
runZonedGuarded(() {
// ゾーンBでゾーンAの Future を利用
future.catchError((error) {
print('ゾーンBでエラーをキャッチ: $error');
});
}, (error, stack) {
print('ゾーンBで未処理のエラーをキャッチ: $error');
});
}, (error, stack) {
print('ゾーンAで未処理のエラーをキャッチ: $error');
});
}
出力結果
ゾーンAで未処理のエラーをキャッチ: Exception: ゾーンAで発生したエラー
詳しい解説
先ほどの例をゾーン毎に色分けすると以下のようになります。
ゾーンB内でfuture.catchError
を使っていますが、future
自体がゾーンAで作成されたものなので、そのエラーはゾーンAに属します。そのため、ゾーンBではエラーをキャッチできず、ゾーンAの未処理エラーハンドラが呼び出されます。
runZoned
との違いは?
runZonedGuarded
を調べる中でrunZoned
という存在を知りました。
runZoned
もゾーンを作る関数ですが、公式ドキュメントを読むと、runZoned
はonError
引数が非推奨で、代わりにrunZonedGuarded
を使うように言われていました。
なぜでしょう。
非推奨のonError
引数が渡された場合、runZoned
はrunZonedGuarded
を使用してエラーをキャッチしようとします。ただし、型引数R
が非nullableの場合、エラーがスローされてnull
が返る可能性があるのです。
何が起きてしまうかを以下のコードで説明します:
void main() {
int result = runZoned<int>(
() {
throw Exception("エラーが発生");
},
onError: (error, stackTrace) {
print("エラーをキャッチ: $error");
},
);
print("結果: $result"); // 実行時エラーになる
}
このコードではR
が非nullable型(int)ですが、runZoned
内でエラーが発生するとnull
を返します。戻り値が非nullable型である場合にnull
を返すことはエラーの原因になります。
戻り値を使用しない設計であれば、この問題は起きません。
しかし、runZoned
にonError
を指定する場合、内部的にrunZonedGuarded
が呼び出される仕組みになっているため、最初からrunZonedGuarded
を使うことが推奨されているというわけです。
runZoned
自体は、エラーハンドリングを必要としない用途では依然として有用ですが、エラーハンドリングを行う場合は、常にrunZonedGuarded
を使うべきです。
runZonedGuarded
とは
まとめ : - ゾーン機能の利用
- 非同期処理のエラーハンドリング(アプリの安全性向上)
- エラーの集中管理(エラーログ収集ツールとの連携)
Discussion