この記事は何?

FlutterアプリでのFirebase Crashlyticsの設定方法は公式サイトや先達の方の素晴らしい記事が非常に参考になりましたが、公式サイトを見ても非致命(non-fatal)エラーに関しての記述が分かりづらかったのでメモしておきます。

困ったこと:非致命(non-fatal)エラーが致命(fatal)エラーとして記録されてしまう

公式サイトの Get Startd のページにある記述通りに設定すると非致命(non-fatal)エラーも致命(fatal)エラーとして記録されてしまいました。

よく見ると非致命エラーに関しては Get Startd ではなく Customize crash reports の方に記載があったのですが、こちらを見ても非同期エラーの非致命エラーについての記載がなく困ってました。

解決方法

まず結論から記載しておくと、以下のように記述しておけば致命エラー、非致命エラーがきちんと分かれて記録されました。

Future<void> main() async {
    WidgetsFlutterBinding.ensureInitialized();
    await Firebase.initializeApp(
        options: DefaultFirebaseOptions.currentPlatform,
    );
    FlutterError.onError = (errorDetails) {
      FirebaseCrashlytics.instance.recordFlutterError(errorDetails);
    };
    PlatformDispatcher.instance.onError = (error, stack) {
      FirebaseCrashlytics.instance.recordError(error, stack, fatal: false);
      return true;
    };
    runApp(MyApp());
}

※実際に設定する際は必ず意図通りに動作するか確認ください

解説というか補足

Crashlytics上で記録されるエラーには以下の2種類があります。(AndroidにはANRがありますがここでは省略します)

  1. クラッシュを伴う致命的なエラー
  2. クラッシュを伴わない非致命的なエラー(補足されない例外)

これらをCrashlytics上で分けて記録するためにはそれ用の設定を行う必要があります。

また、Flutter×Firebase Crashlyticsでは同期エラーと非同期エラーで設定が2箇所に分かれます。

同期エラーの捕捉

公式にあるように、致命エラーを補足する設定は以下になります。なお、自分の環境では非致命エラーも致命エラーとして記録されました。

    FlutterError.onError = (errorDetails) {
      FirebaseCrashlytics.instance.recordFlutterFatalError(errorDetails);
    };

非致命エラー 捕捉するには以下の記述に置き換えます。 こちらで致命、非致命が分かれて記録されるようになりました。

    FlutterError.onError = (errorDetails) {
      FirebaseCrashlytics.instance.recordFlutterError(errorDetails);
    };

非同期エラーの捕捉

公式にあるように、非同期エラー(Flutterフレームワークでキャッチされないエラー)を捕捉する設定は以下になります。 ただし、こちらも自分の環境では非致命、致命問わず致命エラーとして記録されました。

    PlatformDispatcher.instance.onError = (error, stack) {
      FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
      return true;
    };

以下に変えると、非同期エラーについても致命、非致命が分かれて記録されるようになりました。

    PlatformDispatcher.instance.onError = (error, stack) {
      FirebaseCrashlytics.instance.recordError(error, stack, fatal: false);
      return true;
    };

確認用Widgetのサンプル

先達の方の記事を参考にさせていただき、そちらに同期/非同期の観点を追加したものになります。

step1
class DebugCrashTest extends StatelessWidget {
  const DebugCrashTest({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("crash test"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            // 例外を発生させるボタン 非同期
            ElevatedButton(
              onPressed: () async {
                FirebaseCrashlytics.instance.log('ExceptionLog async');
                throw Exception("MyException");
              },
              child: const Text('Throw Error async (non-fatal)'),
            ),
            // 例外を発生させるボタン 同期
            ElevatedButton(
              onPressed: () {
                FirebaseCrashlytics.instance.log('ExceptionLog sync');
                throw Exception("MyException");
              },
              child: const Text('Throw Error sync (non-fatal)'),
            ),
            // アプリをクラッシュさせるボタン 非同期
            ElevatedButton(
              onPressed: () async {
                FirebaseCrashlytics.instance.log('CrashLog async');
                FirebaseCrashlytics.instance.crash();
              },
              child: const Text('Crash async (fatal)'),
            ),
            // アプリをクラッシュさせるボタン 同期
            ElevatedButton(
              onPressed: () {
                FirebaseCrashlytics.instance.log('CrashLog sync');
                FirebaseCrashlytics.instance.crash();
              },
              child: const Text('Crash sync (fatal)'),
            ),
          ],
        ),
      ),
    );
  }
}

さいごに

記事に不明点や誤りなどありましたらTwitterまでDMやリプをお願いします🙇‍♂‍