BEMAロゴ

エンジニアの
成長を支援する
技術メディア

Flutterエンジニアが知っておくべき「Zone」で出来る3つのこと

はじめに

こんにちは、株式会社メンバーズクロスアプリケーションカンパニーOpen in new tabの阿瀬川です。
皆さんは、DartにおけるZone(ゾーン)という概念を知っていますか?
Zoneという概念は、書籍や技術記事に取り上げられることが少ないため、知らない人が多いですが、使いこなすことができればFlutterエンジニアにとって非常に強力な武器となります。

本記事では、Zoneを使うことで出来る代表的な3つのことを、Dart初心者にも理解しやすいように解説します。

Zoneは「安全な空間」のイメージ

Zoneとは「非同期呼び出し全体で安定した状態を維持する環境」であると、Flutter公式ドキュメントにおけるZone classOpen in new tabで記載されています。
以下の図のように、安全な空間(Zone)を展開して、内部で非同期処理を動かすイメージを持ってください。

Zoneを使うことで出来る3つのこと

Zoneで出来ることは様々ですが、以下の3つに分類することができます。

  1. どんなエラーが発生してもハンドリングできる

  2. Dartの基盤となる特定の機能を上書きできる

  3. 引数指定しなくてもアクセスできる共有データを作れる

    次の章よりコードを交えて説明していきます。
    Zoneを実装するには、runZonedGuardedOpen in new tabrunZonedOpen in new tabの関数を使います。実装する処理によって、どちらかの関数を選択して使います。

runZonedGuarded(() {
 // 非同期処理を呼び出す
});

runZoned(() {
 // 非同期処理を呼び出す
});

どんなエラーが発生してもハンドリングできる

Zoneで発生したエラーは必ずハンドリングすることができ、Zoneの外部に影響することはありません。そのため、アプリの予期せぬエラーでクラッシュすることを防ぐことができます。

こちらのコードを見てください。例えば、noErrorHandling()という第三者が作った関数があるとします。
この関数は、本来であればエラーハンドリングしてあるべきですが、第三者は忘れています。
しかし、noErrorHandling()の呼び出しをZoneで行うことで、エラーを必ずハンドリングすることができます。

runZonedGuarded(() {
 // 第三者が作ったメソッドを呼び出す場合
 noErrorHandling();

}, (error, stackTrace) {
 // エラーは、最終的にここに流れ着く
 print("Zoneで捕捉: $error");
});

Future<void> noErrorHandling() {
 throw Exception("エラー");
 // エラーハンドリング忘れてた!
}

この処理であれば、try-catch文で実装可能だと考えるかもしれませんが、非同期処理を扱う場合、それは不可能です。以下のコードを動かしてみてください。

runZonedGuarded(() {
  try {
    // 処理の予約だけして即座にブロックを抜ける
    Future.delayed(Duration(seconds: 1), () {
      throw Exception("1秒後にエラー");
    });
  } catch (e) {
    // この処理は行われない
    // エラー発生時には、既にこのtry-catchブロックは終了している
  }

}, (error, stackTrace) {
  // エラーは、最終的にこのブロックに流れ着く
  print("Zoneで捕捉: $error");
});

try-catch文の中で、非同期処理であるFuture.delayedが呼び出されています。
この処理は、1秒後にExceptionを投げるため、処理の予約だけして即座にtryブロックを抜けます。
その結果catchブロックには辿り着けません。これが、非同期処理でZoneが必要な理由です。

次に実際の現場で用いられる使われ方を紹介します。

第二引数のエラーハンドリングを行う箇所で、errorとstackTrace(エラーが発生するまでの関数の呼び出し履歴)を受け取ることができます。
現場ではこれらをAPM(アプリケーションパフォーマンス管理)に渡し活用します。
APMとは、アプリをリリースした後にユーザーが遭遇したエラーを開発者に自動で通報するシステムです。
Zoneを使い、予期しないエラーを一箇所で一元管理することで、APMという便利なツールを使うことができます。

void main() {
  runZonedGuarded(() {
    // 非同期でエラーが発生
  }, (error, stackTrace) {
    // APMにerrorとstackTraceを渡す処理を記述する
  });
}

Dartの基盤となる特定の機能を上書きできる

Zone内で使われる、Dartの基盤となる特定の機能(printやTimer)を上書きすることができます。

例えば、全ての非同期処理における標準出力のprint()を上書きして、「何も処理をしない」という挙動にすることができます。本番環境のアプリでは標準出力を使わないため、何も処理をしないことで動作を軽くすることができます。
実装には、runZonedOpen in new tabZoneSpecificationOpen in new tabを使用します。
ZoneSpecificationの引数にて、上書き内容を記述することができます。以下のコードを見てください。6行目で何も処理を書かないことで、print()を無効化することができます。

5行目に複数の引数が登場していますが、これは複雑な上書き処理を記述をするためにあります。本記事では、詳細を割愛します。

runZoned(() {
    print("ユーザーのログインに失敗しました。");
  },
  zoneSpecification: ZoneSpecification(
    print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
      // 何もしない
    },
  ),
);

引数指定しなくてもアクセスできる共有データを作れる

Zoneでは、引数指定しなくてもアクセスできる共有データを作ることができます。

実装には、runZonedOpen in new tabとzoneValuesを使用します。zoneValuesを使うことで、各Zone内だけで使えるMap型の値を保存できます。
以下のコードを見てください。user_idに引数なしでアクセスすることができます。
これにより、関数の中で関数を呼び出し、引数のやり取りを何度も行うバケツリレーの問題を解消することができます。

runZoned(() {
  printUserId();
}, zoneValues: {#user_id: "taro_tanaka_012345"});

void printUserId() {
  // 引数なしでデータにアクセスできる
  final String userId = Zone.current[#user_id];
  print("現在のユーザーは$userIdです");
}

ただ、zoneValuesに関してはアプリ開発で直接書くことは少ないかもしれません。しかし、外部パッケージやフレームワークの裏側では頻繁に使われているため、知っておくことは高度な設計をする上で大切になります。

まとめ

DartにおけるZoneで出来ることを、3つに分類して紹介しました。

正直な話、1つ目のエラーハンドリングは見かけたり実装することがあるかもしれませんが、Dartの基盤となる特定の機能の上書きと共有データの作成は使う機会が少ないかもしれません。

しかし、あなたが毎日使っているパッケージの裏側では、必ずと言っていい程、このZoneが「安全」と「便利」を支えています。

本記事はZoneの入門を目的としています。この機会にZoneについて深く調べて、より堅牢で簡潔なアプリ開発を目指しましょう。

この記事が役に立ったと思ったら、
ぜひ「いいね」とシェアをお願いします!

リンクをコピーXでシェアするfacebookでシェアする

この記事を書いた人

阿瀬川祥永
阿瀬川祥永
2025年にメンバーズに新卒で入社。現在はCross ApplicationカンパニーでFlutter技術をメインにモバイルアプリ開発支援を担当。
詳しく見る
ページトップへ戻る