Flutter×Firebase(Crashlytics)でFlutterアプリのエラーやクラッシュ情報を一元管理してみる

---
# :book: Flutterの記事を整理し本にしました :book:
https://zenn.dev/kazutxt/books/flutter_practice_introduction

* <font color="red"><b>本稿の記事を含む様々な記事を体系的に整理し本にまとめました</b></font>
* 今後はこちらを最新化するため、<font color="red"><b>最新情報はこちらをご確認ください</b></font>
---

## まとめ記事
* [Flutter系の記事のまとめ](https://qiita.com/kazutxt/items/524acb1c134015682ccf)

# はじめに
**Firebaseのクラッシュレポートの統合ソリューション機能を提供するcrashlyticsについて解説します。**

Firebase Crashlytics(以下Crashlytics)の概要を確認した上で、アプリのエラー情報を連携する方法を解説します。

# 概要
Crashlyticsは、アプリで発生した例外やエラーのスタックトレースやログを一元管理するための環境を提供します。

## 有効化手順
有効化は特段必要ありません。情報が送られると自動で有効化されます。

# 準備
## パッケージのインストール
まずは、必要なパッケージをインストールします。
下記の通り、`firebase_crashlytics`のパッケージを入れます。

```diff yaml:pubspec.yml
//中略
dependencies:
  flutter:
    sdk: flutter
　firebase_core: ^1.19.1
+ firebase_crashlytics: ^2.8.5
//中略
```

# 実装
Crashlyticsは、ログを送信するための仕組みを`main`メソッドに設定をし、キャッチされた例外やクラッシュレポートなどを送信します。

ソースコードの全体像は下記のようになります。

```dart:main.dart
import 'dart:async';
import 'dart:core';

import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';

import 'package:firebase_crashlytics/firebase_crashlytics.dart';

void main() async {
  // runZonedGuardedで全体を新しいエラーゾーンを定義
  await runZonedGuarded(() async {
    WidgetsFlutterBinding.ensureInitialized();
    await Firebase.initializeApp(
      options: DefaultFirebaseOptions.currentPlatform,
    );
    //Flutterでキャッチされた例外/エラー
    FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError;
    runApp(const MyApp());
  }, (error, stackTrace) {
    //Flutterでキャッチされなかった例外/エラー
    FirebaseCrashlytics.instance.recordError(error, stackTrace);
  });
}

// MyApp,MyHomePageはデフォルトから変更がないため省略

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Center(
            child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            // 例外を発生させるボタン
            ElevatedButton(
              onPressed: () {
                FirebaseCrashlytics.instance.log('ExceptionLog');
                throw Exception("MyException");
              },
              child: const Text('Throw Error'),
            ),
            // アプリをクラッシュさせるボタン
            ElevatedButton(
              onPressed: () {
                FirebaseCrashlytics.instance.log('CrashLog');
                FirebaseCrashlytics.instance.crash();
              },
              child: const Text('Crash'),
            ),
          ],
        )));
  }
}
```
サンプルアプリには、例外を発生させるボタンとアプリをクラッシュさせるボタンの2つを作成しています。
例外の方は、シンプルに`throw Exception`をしているのに対して、クラッシュの方は、`FirebaseCrashlytics.instance.crash()`で意図的なアプリの停止をしています。

Crashlyticsへのレポートの連携経路は、2種類あります。
1. Flutterでキャッチされた例外/エラー
    * `main`メソッドの中での`FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError`で記録
2. Flutterでキャッチされなかった例外/エラー
    * `runZonedGuarded`で`runApp`などを囲んで新たなエラーゾーンを定義した上で、
`FirebaseCrashlytics.instance.recordError`で記録

**場合によっては、データが送信されるのが、クラッシュ後に再度アプリを立ち上げた時になるので、データが到着していない場合は、再度アプリを起動してください。**

# 動作イメージ
例外のボタンとクラッシュのボタンを押してアプリを停止させます。
その後、再度アプリを立ち上げて、レポートを送信させます。
![crashlytics-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/841df7ad-71e9-8989-aeae-4611bc55e467.png)

続いて、ブラウザからCrashlyticsに行くと、ダッシュボードからそれぞれの詳細を確認できます。
発生させた例外の詳細を確認すると、スタックトレースやログを確認することができます。
![crashlytics-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/5556c5c7-d4e7-68b0-f5a7-205f0186b6d6.png)
![crashlytics-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/c6cbc43d-99d9-fa81-8b0c-e1cd4ef94aec.png)
![crashlytics-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/cf4dc0ba-4f4e-bec4-3dce-5e294e3b9246.png)
![crashlytics-5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/87787628-3fbf-cba2-1d53-0f200485f3ce.png)

**今回はキーについてはデフォルトのまま利用しましたが、カスタムキーを設定することも可能です。
キーを設定することで、問題を分類し、検索などをしやすくできます。**

クラッシュレポートの方も、同様に、スタックトレースやログを確認することができます。
![crashlytics-6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/cc0b86da-f2df-a723-dd8e-94e7a05dff63.png)


なお、AndroidとiOSは別のアプリなので、問題は別々に管理されるため、表示の切り替えが必要です。
![crashlytics-7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/8c49d590-52d0-b78e-0664-85688169c7cc.png)