新しいFlutterのエラーハンドリング方法
tags: Flutter
※ 最新情報は、Flutter公式のHandling errors in Flutterや、Crashlyticsをご利用ならFirebaseCrashlyticsを使い始めるを見るのをおすすめします。
久しぶりにFlutterのドキュメントHandling errors in Flutterを見たら、Flutterのエラーハンドリング方法が新しくなっていました。 以前の方法のrunZonedGuarded()がなくなっているけど大丈夫なの?と不安になったので、どう変わったのか整理してみました。
結論
- Flutterが発生させるエラーはこれまで通りFlutterError.onErrorでハンドリングする。
- runZonedGuarded()やIsorate.current.addErrorListener()でハンドリングしていたエラーは、PlatformDispatcher.instance.onErrorでハンドリングできるようになった。
新しい方法
Flutter 3.3からの新しい方法は以下の通りです。 Handling errors in Flutterほぼそのままです。
この例ではクラッシュレポートの送信にCrashlyticsを利用しています。Crashlyticsについては深くは触れません。FirebaseCrashlyticsを使い始めるをご参照ください。
void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(); final defaultFlutterErrorHandler = FlutterError.onError; FlutterError.onError = (errorDetails) { defaultFlutterErrorHandler?.call(errorDetails); final isSilent = errorDetails.silent; FirebaseCrashlytics.instance.recordFlutterError(errorDetails, fatal: !isSilent); }; PlatformDispatcher.instance.onError = (error, stack) { FirebaseCrashlytics.instance.recordError(error, stack, fatal: true); return true; }; runApp(const MyApp()); }
↑runZonedGuarded()でハンドリングするのではなくPlatformDispatcher.instance.onErrorでハンドリングするようになっています。
以下によると、runZonedGuarded()でcustom zoneを作る方法はパフォーマンスに影響するため、Flutter3.3ではこの方法になったようです。
custom Zones were detrimental to a number of optimizations in Dart’s core libraries, which slowed down application start-up time. https://medium.com/flutter/whats-new-in-flutter-3-3-893c7b9af1ff
以前の方法
下記、以前の方法です。 FlutterFireの現時点でアーカイブになっているUsing Firebase Crashlyticsにはこの記載が残っていますね。
void main() { runZonedGuarded<Future<void>>(() async { WidgetsFlutterBinding.ensureInitialized(); Firebase.initializeApp(); if (kDebugMode) { await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(false); } final defaultErrorHandler = FlutterError.onError; FlutterError.onError = (FlutterErrorDetails errorDetails) { defaultFlutterErrorHandler?.call(errorDetails); final isSilent = errorDetails.silent; FirebaseCrashlytics.instance.recordFlutterError(errorDetails, fatal: !isSilent); }; runApp(MyApp()); }, (error, stackTrace) { FirebaseCrashlytics.instance.recordError(error, stackTrace, fatal: true); }); }
↑runZonedGuardedで囲まれています。
補足: runZonedGuarded()について
この中で発生しハンドリングされなかった同期のエラーと非同期のエラーをハンドリングできるようになります。
The onError function is used both to handle asynchronous errors by overriding ZoneSpecification.handleUncaughtError in zoneSpecification, if any, and to handle errors thrown synchronously by the call to body. https://api.flutter.dev/flutter/dart-async/runZonedGuarded.html
気になったので動かしてみたところ、build()内やButtonのonPress()内で以下のような形で発生したエラーがrunZonedGuarded()のonErrorに来ます。
final future1 = Future.value(1).then((value) => throw 'Error in main() future'); // await&try-catchしない。catch()もしない。
補足: Isolate.current.addErrorListenerについて
上のアーカイブされたドキュメントのErrors outside of FlutterにIsolate.current.addErrorListenerというのがありました。 確認した範囲だと、下記の通りmain()内でthrowされたErrorをハンドリングできるようになるみたいです。
これも、Flutter3.3からは、PlatformDispatcher.instance.onErrorでハンドリングできるようになっていました。
void main() { Isolate.current.addErrorListener(RawReceivePort((pair) async { final List<dynamic> errorAndStacktrace = pair; print('***** Isolate.current.addErrorListener: ${errorAndStacktrace.first}'); }).sendPort); runApp(const MyApp()); throw 'Error'; // これが上のIsolate.current.addErrorListener()にくる。 }
おわりに
まとめとしては、「結論」と同じですが、FlutterError.onErrorとPlatformDispatcher.instance.onErrorを使うということになります。