【Mac不要】FlutterアプリにFirebase Crashlyticsを組み込んでiOSとAndroidに実機配備する手順 - Codemagic編

# はじめに

※この記事は、以下の記事の続編です。

[【Mac不要】FlutterアプリにFirebase SDKを組み込んでiOSとAndroidに実機配備する手順 - Codemagic編](https://qiita.com/kami_teru/items/d3b72f5c0aa97e6c1c08)

この記事ではその後、FlutterアプリにFirebase Crashlyticsを組み込み、iOS, Androidの両端末のクラッシュ情報をFirebaseコンソールに連携できるようになるまでの手順を説明します。

では早速始めてみましょう。

## 謝辞

#### Firebase Crashlytics - Android

Android向けの作業を進めるにあたっては、以下の記事がとても助けになりました。ありがとうございます。

* [Firebase Crashlyticsを導入する - @tnagao3000](https://qiita.com/tnagao3000/items/ad492179d6d929616268)
* [Serverless連載4: Firebase CrashlyticsでAndroidアプリのエラーログをさくっと収集する - フューチャー開発者ブログ](https://future-architect.github.io/articles/20200330/)

#### Firebase Crashlytics - iOS

iOS向けの作業を進めるにあたっては、以下の記事がとても助けになりました。
ありがとうございます。

* [FlutterでCocoaPodsのライブラリを使う](https://ryuta46.com/1158)

## 前提

この記事は、以下に当てはまる人向けの iOS/Android 対応アプリにFirebase Crashlyticsを組み込む手順を紹介します。

* 開発機はWindowsだ
* **Macを持っていない**
* Flutterで開発したい
* Apple Developer Programに既に登録している。

## 開発環境の前提

以下の2本の記事の手順に沿ってFlutterアプリをCodemagicでビルドし、さらに自身のiOS端末/Android端末のアプリの利用状況(Analytics)がFirebaseに連携されている状態にしておいてください。

1. [【Mac不要】開発中のFlutter製アプリをiOSとAndroidに実機配備する手順 - Codemagic編](https://qiita.com/kami_teru/items/c7ed113d0ecdf7e48ff0)
1. [【Mac不要】FlutterアプリにFirebase SDKを組み込んでiOSとAndroidに実機配備する手順 - Codemagic編](https://qiita.com/kami_teru/items/d3b72f5c0aa97e6c1c08)


## ソースコードの公開

この記事の手順で作成したソースコードはGithubで公開しています。併せてご参照ください。
https://github.com/atsuteru/flutter_firebase_0507/tree/Qiita-FirebaseCrashlytics-v1.0

# 本文

## FlutterアプリにFirebase Crashlyticsを組み込む

FlutterにFirebase Crashlyticsを組み込むために、まずはライブラリのページを確認するようにしましょう。Google検索のキーワードは`Flutter Firebase Crashlytics`で大丈夫。以下のページが見つかればOKです。

[firebase_crashlytics | Flutter Package - Dart Pub](https://pub.dev/packages/firebase_crashlytics)

なぜページを確認するかというと、次の2点です。

* 最新バージョンのバージョン番号を調べる。
* 最新バージョンの導入方法を学ぶ。

バージョン番号は、タイトルに記載されています。
2020/5/11時点のタイトルは次のようになっていました。

```
firebase_crashlytics 0.1.3+3
```

つまり最新バージョンは `0.1.3+3` だということ。※最後の`+3`までがバージョン番号です。

またそのページの説明の中に、以下の見出しがありますので、それを読み解いて作業を進めることになります。

* Usage ・・・ ライブラリの使い方
* Import the firebase_crashlytics plugin ・・・ Flutterアプリへの組み込み手順
* Android integration ・・・ Androidで動作させるための追加の手順
* iOS Integration ・・・ iOSで動作させるための追加の手順
* Use the plugin ・・・ ライブラリを利用する実装方法

ではこれでもうできましたね！お疲れ様ですっ。


・・・


となればいいのですが、何かと上手くいかなかったりします。それだけ進化のスピードが速いということですね！（ポジティブ

というわけで、以下、私がいろいろ試した中で、うまくいった最短の手順を紹介します。

## FlutterアプリにFirebase Crashlyticsを組み込む

この手順はライブラリの[installing](https://pub.dev/packages/firebase_crashlytics#-installing-tab-)の手順に沿ってます。

Flutterにライブラリを組み込むには、`pubspec.yaml`を編集します。`dependencies`に最新のバージョン`firebase_crashlytics: ^0.1.3+`を追記しましょう。追記する箇所は`flutter`,`firebase_core`よりも後にしないと、私はうまくいきませんでした。

```yaml:pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  firebase_core: ^0.4.4+3
  firebase_crashlytics: ^0.1.3+3
```

## Flutterアプリに、FirebaseコンソールのCrashlyticsと連携するためのコードを追加する

この手順はライブラリの[Use the plugin](https://pub.dev/packages/firebase_crashlytics#use-the-plugin)の手順に沿っています。

Flutterアプリが実行中にクラッシュしたことを検出し、FirebaseコンソールのCrashlyticsに連携できるようにするために、以下のコードを追加します。

**あと、「▼▼▼」「▲▲▲」などが付いたコメントは、この記事での説明用ですので、あなたのコードには書かないでください。念のため(^^;**

**2020/06/04 修正**
runZonedのonError引数がdeprecatedとしてマークされていたため、ソースコードを更新しました。参考: [Migrate runZoned calls to runZonedGuarded](https://github.com/flutter/flutter/issues/53185)

```dart:lib/main.dart
//▼▼▼ 以下、修正前のコード ▼▼▼
//import 'package:flutter/material.dart';
//void main() => runApp(MyApp());
//▼▼▼ 以下、修正後のコード ▼▼▼
import 'dart:async';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/material.dart';
void main() {
  Crashlytics.instance.enableInDevMode = true;
  FlutterError.onError = Crashlytics.instance.recordFlutterError;
  runZonedGuarded(() async {
    runApp(MyApp());
  }, (e, s) async => await Crashlytics.instance.recordError(e, s));
}
```

上記の各コードの説明は、ライブラリのページに書いてありますので、割愛します。

## Flutterアプリに、Crashlyticsとの連携を試すためのテストコードを追加する

この手順は任意ですが、もしここまでを私の記事に沿って作ってこられた場合は、ぜひやってみてください。

この手順は、ライブラリの[Example](https://pub.dev/packages/firebase_crashlytics#example)で公開されているサンプルコードを取り入れるものです。

以下の「追加ココカラ」「追加ココマデ」の間の部分を、実装しましょう。この実装はFirebase Crashlyticsライブラリのいろんな機能を実験できるボタンを画面に表示します。

```dart:lib/main.dart
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
//▼▼▼ 追加ココカラ ▼▼▼
            Text(
              'Firebase Crashlytics Tests:',
            ),
            FlatButton(
                child: const Text('Key'),
                onPressed: () {
                  Crashlytics.instance.setString('foo', 'bar');
                }),
            FlatButton(
                child: const Text('Log'),
                onPressed: () {
                  Crashlytics.instance.log('baz');
                }),
            FlatButton(
                child: const Text('Crash'),
                onPressed: () {
                  // Use Crashlytics to throw an error. Use this for
                  // confirmation that errors are being correctly reported.
                  Crashlytics.instance.crash();
                }),
            FlatButton(
                child: const Text('Throw Error'),
                onPressed: () {
                  // Example of thrown error, it will be caught and sent to
                  // Crashlytics.
                  throw StateError('Uncaught error thrown by app.');
                }),
            FlatButton(
                child: const Text('Async out of bounds'),
                onPressed: () {
                  // Example of an exception that does not get caught
                  // by `FlutterError.onError` but is caught by the `onError` handler of
                  // `runZoned`.
                  Future<void>.delayed(const Duration(seconds: 2), () {
                    final List<int> list = <int>[];
                    print(list[100]);
                  });
                }),
            FlatButton(
                child: const Text('Record Error'),
                onPressed: () {
                  try {
                    throw 'error_example';
                  } catch (e, s) {
                    // "context" will append the word "thrown" in the
                    // Crashlytics console.
                    Crashlytics.instance
                        .recordError(e, s, context: 'as an example');
                  }
                }),
//▲▲▲ 追加ココマデ ▲▲▲
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
```

## Android向けの追加の導入手順を実施する

Android向けには、以下の手順が必要です。

この手順はライブラリの[Android integration](https://pub.dev/packages/firebase_crashlytics#android-integration)の手順に沿っています。

### 1) Android向けのライブラリの組み込みを定義する

以下の「追加ココカラ」「追加ココマデ」の間の部分を、追加しましょう。

```gradle:android/build.gradle
buildscript {
    repositories {
        google()
        jcenter()
//▼▼▼ 追加ココカラ ▼▼▼
        maven {
            url 'https://maven.fabric.io/public'
        }
//▲▲▲ 追加ココマデ ▲▲▲
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.0'
        classpath 'com.google.gms:google-services:4.3.3'
//▼▼▼ 追加ココカラ ▼▼▼
        classpath 'io.fabric.tools:gradle:1.26.1'
//▲▲▲ 追加ココマデ ▲▲▲
    }
}

allprojects {
    repositories {
        google()
        jcenter()
//▼▼▼ 追加ココカラ ▼▼▼
        maven {
            url 'https://maven.fabric.io/public'
        }
//▲▲▲ 追加ココマデ ▲▲▲
    }
}
```

### 2) Android向けのプラグインの読み込みを定義する

`apply plugin`の箇所は、`com.google.gms.google-services`よりも前であることに注意してください。

```gradle:android/app/build.gradle
apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
//▼▼▼ 追加ココカラ ▼▼▼
apply plugin: 'io.fabric'
//▲▲▲ 追加ココマデ ▲▲▲
apply plugin: 'com.google.gms.google-services'

//▲▼▲(中略)▲▼▲

dependencies {
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
    implementation 'com.google.firebase:firebase-analytics:17.2.2'
//▼▼▼ 追加ココカラ ▼▼▼
    implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
//▲▲▲ 追加ココマデ ▲▲▲
```

ちなみに、`fabric`は、Firebase Crashlyticsの旧サービスです。現在は`Firebase Crashlytics`に移行が進められていますが、内部的にはまだ`fabric`のライブラリを利用しているということですね。

## iOS向けの追加の導入手順を実施する

次はiOS向けの導入手順ですが・・・

結論から言うと、**iOS向けには追加のライブラリの導入手順は不要**です。

なぜならば、FlutterにFirebase関連のパッケージを組み込んだ状態でのiOS向けビルドは、その組み込み情報をもとにPodfileを自動生成し、iOS向けのライブラリの組み込みまでを自動的にやってくれるから、です。

なのでこれは蛇足ですが、`iOS`ディレクトリ直下にPodfileをコミットしようものなら、上記の自動生成がスキップされてしまい、ビルドが通らなくなりますのでご注意を。

なお・・・`Podfile`とは、iOSアプリのライブラリを管理できるCocoaPodsというツールの設定ファイルで、`ios/Runner.xcodeproj/project.pbxproj`の書き換えをやってくれるものです。WindowsでCocoaPodsを動作させるのはいばらの道ですので、Macを持たない我々は、FirebaseがCodemagic上でそれを自動的にやってくれるという恩恵にあずかりましょう。。。

## iOS向けに不要になったコードを削除する

とは言いつつ1つだけ、iOS向けにやっておかないといけない手順があります。

以下のように、コードを2行削除してください。（「この行を消す！」の2行）

```objective_c:ios/Runner/AppDelegate.m
#import "AppDelegate.h"
#import "GeneratedPluginRegistrant.h"

//@import Firebase; ←この行を消す！

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [GeneratedPluginRegistrant registerWithRegistry:self];
  // [FIRApp configure]; ←この行を消す！
  // Override point for customization after application launch.
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

@end
```

私の記事の手順に沿ってやってこられた方はお気づきだと思いますが、これは「Firebase SDK」の組み込みの手順として追加した２行です。

今回、Firebase CrashlyticsをFlutterに組み込んだことによって、不要となったようです。（私はこれを削除しないとうまくいかなかった）

## FilebaseコンソールでCrashlyticsと連携できたことを確認する

連携前にFilebaseコンソールでアプリのCrashlyticsページを覗いてみると、次の画面が表示されました。

![flutter-firebase-0507 - Firebase コンソール - Google Chrome 2020_05_11 22_45_25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109362/5c9db79e-f84a-332e-ff90-b95841a47bcb.png)

SDKの組み込みは先程までの手順で終えているので、ソースコードをpushし、Codemagicによるリリースビルドの結果を待ちましょう。

・・・ビルドが通りましたね！
![flutter-firebase-0507 - Firebase コンソール - Google Chrome 2020_05_11 23_31_54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109362/7065ca3c-2104-9f1f-2ea6-f42c9fd0dec5.png)

### Androidでの確認

Codemagicから届いたビルド成功の通知メールから、再インストールしました。
<img width="320" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109362/5675320f-6827-b955-d5fc-9d3938d29eb4.jpeg">

そしてFirebaseコンソールに戻ると・・・「インストールが完了しました」の表示が！ちゃんと連携できましたね。
![flutter-firebase-0507 - Firebase コンソール - Google Chrome 2020_05_11 23_35_08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109362/5ba3fee9-3d67-9dfd-5a3e-7aef5f1e8d26.png)

Crashlyticsダッシュボードに移動してみましょう。
![flutter-firebase-0507 - Firebase コンソール - Google Chrome 2020_05_11 23_35_37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109362/3a6f6481-25e4-a3ce-eb6f-a44388ba656c.png)

クラッシュはまだありませんね。

**ここで一つ注意です！**

このページはデフォルトで、**アプリが異常終了したレベルのクラッシュ**だけを表示するようにフィルタされています。**青いラベル「イベントの種類"クラッシュ"」の「×」を押して、フィルタを解除しておきましょう。**

では、実際に"クラッシュ！"・・・はできませんが、仕込んでおいたボタンでアプリでエラーを起こしてみましょう。

**アプリは一度終了し、起動しなおしてください。（初回起動のみ、うまく連携されないようです）**

そしてアプリの画面の「Key」「Log」「Crash」「Throw Error」「Async out of bounds」「Record Error」をガンガンタップしてみましょう。

すると数分もたたないうちに・・・Crashlyticsページに連携されましたね！
![flutter-firebase-0507 - Firebase コンソール - Google Chrome 2020_05_11 23_50_37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109362/4fda8695-ab5c-0d08-c0b2-656cdef73d01.png)

エラーの詳細も、このように確認することができます。
![flutter-firebase-0507 - Firebase コンソール - Google Chrome 2020_05_11 23_52_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109362/3c60afd0-ab32-72a8-54c8-28d159301a37.png)

### iOSでの確認

Codemagicから届いたビルド成功の通知メールから、再インストールしましょう。

※iOSアプリは、TestFlightの手順（記事：[【Mac不要】開発中のFlutter製アプリを仲間のiOS端末に実機配備する手順 - Codemagic&TestFlight編](https://qiita.com/kami_teru/items/6d5c9b24f711f1270697)）をすでに実施している場合は、TestFlightから「Update」することでこのバージョンをインストールしましょう。それ以外はの方はCodemagicから届くメールからインストールしてください。

<img width="320" alt="unnamed(3).jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109362/07b85879-d512-8f49-525d-388abab4a361.jpeg">

iOSアプリでも同じように、アプリの画面の「Key」「Log」「Crash」「Throw Error」「Async out of bounds」「Record Error」をガンガンタップしてみましょう。

私の場合は少し時間がかかりましたが・・・Crashlyticsページに連携されましたね！
![flutter-firebase-0507 - Firebase コンソール - Google Chrome 2020_05_12 0_22_00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109362/43206e7f-bcb7-4e4f-dfa2-2d71fd4a396a.png)

iOSの場合の詳細ページはこんな感じです。
![flutter-firebase-0507 - Firebase コンソール - Google Chrome 2020_05_12 0_25_36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109362/0ff53b49-4198-4373-a7d5-0663797818c2.png)

# おわりに

これで、本記事の目的は達成できました。しかし、これでやっと開発のスタートラインに立ったにすぎません。

この記事の続編として、あと１本を予定しているので、乞うご期待！

* Google Photo APIと連携し、利用者のフォトアルバムにアクセスする

最後に、この記事がFlutterを始められる方にとって少しでも助けになれば、幸いです。