【Flutter】FCMで初めてのプッシュ通知 2020年12月版

[Flutter #2 Advent Calendar 2020](https://qiita.com/advent-calendar/2020/flutter-2) の 16日目 の記事となります。
Flutterが気になる方は [Flutter #1](https://qiita.com/advent-calendar/2020/flutter) や[Flutter #3](https://qiita.com/advent-calendar/2020/flutter-3)もありますので、ぜひご覧になってください。

## 概要
私が初めてPush通知を実装したとき苦労した記憶がありました。そこで初めてのモバイルアプリ開発をFlutterで行う人に対しての「この記事を参考にすれば最低限のPush通知が確認できる」という内容にしました。
また、基本的には下記pluginページの流れと同じものになります。
https://pub.dev/packages/firebase_messaging

※ このFCM用の設定を本番、開発で切り替えたい場合は[こちらの記事](https://qiita.com/yana1316/items/314fbe3eb8d4a95ea78c)を参考にしてください。

### 前提

- Flutterの環境構築は完了している
- Firebaseを使ってPush通知を実装したい

### 環境
```
$ flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 1.22.4, on Mac OS X 10.15.7 19H2 darwin-x64, locale en-JP)
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 12.1)
[✓] Android Studio (version 4.1)
[✓] Connected device (1 available)      
```

### サンプルコード
https://github.com/yana1316/flutter_fcm_first_sample

## Flutterプロジェクトの作成
`Create New Flutter Project`を選択します。
![fcma01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315258/fcbffc98-c23e-2a6d-49a1-d146259e1e4b.png)
次にどのタイプのプロジェクトを作成するか聞かれるので`Flutter Application`を選択します。
`Project name`は`flutter_fcm_first_sample`としました。
`Package name`は`com.yana1316.flutter_fcm_first_sample`としました。この名前はFirebaseやXcodeの設定でも使いますので覚えておいて下さい。


## Firebaseでプロジェクトの作成

**Firebaseのアカウントの作成**
Googleアカウントが無い方は作成してください。
**プロジェクトの追加**
プロジェクト名を`flutter-fcm-first-sample`としました。Google Analyticsの追加は、どちらでも大丈夫です。


## Android用設定
### Firebase設定
**アプリの登録**
プロジェクト追加後、画面上からAndroid用アプリを登録します。
![fcm01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315258/50642a7f-80f9-f260-eff2-bedc0f02925f.png)
**Android パッケージ名** 
Android StudioでProject作成時に設定した`Package name`を入力します。
<details><summary>パッケージ名を忘れてしまった場合</summary>Android Studioで確認できます。
![fcm02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315258/2a301a80-9c7a-af09-d854-e1e802a893a5.png)
</details>

**設定ファイルのダウンロード**
`google-services.json`をダウンロードし、`[project]/android/app/`に置きます。
![flutter_fcm_first_sample_–_google-services_json__flutter_fcm_first_sample_android_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315258/a9b910a6-6aa5-9b21-8070-f60d6263c3f8.png)
`google-services.json`は機密情報ではありませんが、公開されたgit等で管理しないようにしましょう。（いろいろと狙われる原因になりがちですので）

**Firebase SDK の追加（スキップ）**
何もせず`次へ`を押してスキップします。

**次のステップ**
何もせず`コンソールに進む`を押します。

### Android Studioでの設定
`[project]/android/build.gradle`に追記します。

```gradle
dependencies {
    classpath 'com.android.tools.build:gradle:3.5.0'
    classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    classpath 'com.google.gms:google-services:4.3.4' // 追記
}
```

<details><summary>`com.google.gms:google-services` の最新バージョン</summary>以下のページの`Google Play services plugin`で最新バージョンを確認することができます。  
https://firebase.google.com/support/release-notes/android#latest_sdk_versions
</details>

`[project]/android/app/build.gradle`に追記します。

```gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
apply plugin: 'com.google.gms.google-services'  // 追記
```

`[project]/android/app/src/main/AndroidManifest.xml`の40行目付近に追記します。

```xml
<intent-filter>
    <action android:name="FLUTTER_NOTIFICATION_CLICK" />
    <category android:name="android.intent.category.DEFAULT" />
</intent-filter>
```

※ 今回、バックグラウンド処理用の設定は省略しています。詳細は以下から確認してください。
https://pub.dev/packages/firebase_messaging#optionally-handle-background-messages

##iOS用設定
### Apple Developer Programでの設定
**アカウントの作成**
Apple IDでも開発できるのですが、たぶんPush通知を行うには有料のApple Deeloper Programに登録する必要がありそうです。下記ページの`Appの高度な機能とサービス`にPush通知で利用する`Push Notifications`機能が含まれている気がします。
https://developer.apple.com/jp/support/compare-memberships/

Apple Developer Programのアカウントを作成したらログインして、以下の`Certificates, Identifiers & Profiles`に移動します。
![fcm06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315258/30c114ac-ba0a-8c39-b2cb-64088a87f2a3.png)
**Keys の作成**
`Certificates, Identifiers & Profiles`に移動後、左メニューの`Keys`をクリックします。
`+`ボタンを押して新しいKeyを追加します。Keyの名前はご自由にですが`product key`とか`main key`とかにするのでしょうか。
※ このKeyは、1つのアカウントで2つまでしか作れません。つまり複数のアプリを作ったとしても共通のKeyとなります。
※ 今回はFirebase FCMを使ってのPush通知となりますので、`APNs用証明書(.cer)(.p12)`は使わず、Firebaseがおすすめする`APNs認証キー(.p8)`を使います。

### Xcodeの設定
Android StudioのProject Windowから下記の`[project]/ios`を右クリックし`Flutter`内の`Open iOS module in Xcode`をクリックします。すると該当ProjectのXcodeが開きます。
![fcm07a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315258/423ede81-00c1-54bb-2a98-15f21433ef1b.png)

**アカウント設定**
Xcode画面左上のほうにある`Show the project navigator`をクリックし`Runnner`をクリックします。
![Runner_xcodeproj.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315258/b77088b2-5267-0377-62bc-4173bef8f4b0.png)
`Automatically manage signing`にチェックを付け、Teamで自分の`Apple Deeloper Program アカウント`を選択（追加）します。
<details><summary>`Automatically manage signing`での設定について</summary>
※ Signingにはいくつかの方法がありますが、Autoでの説明をしています。この他に`Apple Developer Program`に自分で証明書を用意する方法、`fastlane`などを使ってチーム内で証明書を簡単に共有しつつ設定する方法などがあります。
※ `Automatically manage signing`にチェックをつけるとXcodeが`Apple Developer Program`にログインし、証明書をアップロードしてくれます。`Identifiers`のページを見ると `XC`から始まる証明書が登録されていますが、これは`Xcode`が登録したもので、`XC`は`Xcode`略称だと思われます。
</details>

**Capabilityの追加**
以下の`+Capability`から`Push Notifications`と`Background Modes`を追加してください。
![fcm08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315258/4715f5e6-9372-4120-c0ae-3fd6ec752b5f.png)

### Firebaseの設定
**iOSアプリを追加**
Firebaseの画面から`+アプリの追加`を選択しiOSを追加ます。
**アプリの登録**
`iOSバンドルID`はXcodeのSigning画面内にある`Bundle Identifier`となります。
**設定ファイルのダウンロード**
`GoogleService-Info.plist`をダウンロードし、Xcodeの下記の場所に移動します。
![fcm08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315258/4fe47367-4094-dcdd-b5bc-96d52e89f7a3.png)

**Firebase SDK の追加**
何もせず`次へ`を押してスキップします。
**初期化コードの追加**
何もせず`次へ`を押してスキップします。
**次のステップ**
何もせず`コンソールに進む`を押します。

**APNs認証キー の登録**
Firebaseメニューの歯車から`プロジェクトを設定`を選択します。
![fcm09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315258/ad305d1e-1640-5807-9854-dfe322b22364.png)
`Cloud Messaging`の`iOSアプリ`から`APNs認証キー`の`アップロード`を選択します。
![fcm10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315258/96d2d8f8-399b-4774-a8f6-f926451b4676.png)
`Apple Developer Program`の`Keys`で作成した.p8ファイルをアップロードし、`キーID`は下記の`Keys`の詳細画面から確認します。
![fcm11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315258/e2cbc641-14e5-2eea-d985-fd70381d0c81.png)
`チームID`は`Apple Developer Program`内の`Identifiers`にある自動生成された証明をの詳細を開くと下記のように`Team ID`が確認できます。
![fcm13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315258/da419e7b-044c-4282-035e-ab47c18014a8.png)


## Flutterの設定
基本的には、下記のサンプルコードを参考にしてもらえれば大丈夫なはずです。
https://github.com/yana1316/flutter_fcm_first_sample

### .yaml
`pubspec.yaml`に追記します。

```yaml
dependencies:
  flutter:
    sdk: flutter
  firebase_messaging: ^7.0.3 # 追加
```

### main.dart
**Firebase用のSDKを用意**

```dart
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
```

**iOS用 Push通知の許可**
「xxxはあなたにプッシュ通知を送信します。よろしいですか？」を表示させるやつです。

```dart
/// iOS用 PUSH通知の許可確認
_firebaseMessaging.requestNotificationPermissions(
    const IosNotificationSettings(sound: true, badge: true, alert: true));
```

**FCM用Tokenの取得**
FCMサーバーにデバイスID?を送信して、デバイス固有のTokenを取得します。このTokenは、FCMからメッセージを送信するときの宛先となります。

```dart
/// iOS用 PUSH通知の許可確認
/// FCM token の取得
_firebaseMessaging.getToken().then((String token) {
  assert(token != null);
  setState(() {
    _token = token;
  });
  print("token: $_token");
});
```

**メッセージ受診時の処理**
FCMからメッセージを受信したさいの処理を記載します。必要最低限の動作は`onMessage`で確認できます。`onLaunch`や`onResume`、`onBackgroundMessage`などは設定条件もありますので、詳細は[Plugin公式](https://pub.dev/packages/firebase_messaging)をご確認ください。

```dart
_firebaseMessaging.configure(
  onMessage: (Map<String, dynamic> message) async {
    setState(() {
      _message = "onMessage: $message";
    });
    print("onMessage: $message");
  },
  /// 省略
);
```

## 動作確認
### Tokenの取得
Android Studioでシミュレーターを実行すると、画面上にtokenが表示されます。
![fcm15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315258/281a1c88-1e84-1905-aa6e-466e4fcb5f3f.png)

またAndroid StudioのConsoleにtokenがprintされますのでコピーしておきます。
![fcm14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315258/48e851e8-a9d7-5bcd-7032-45f7b3020164.png)

### Firebase Cloud Messagingからの送信テスト
FirebaseのCloud Messageingにある`Send your first message`を開きます。
![fcm16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315258/0dd1c75e-4514-00e8-4582-bbcb6582108a.png)

タイトルやテキストを適当に埋めて`テストメッセージを送信`ボタンを押します。
![fcm17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315258/97381415-0eb0-0320-66df-5b828eeab9a4.png)

表示されたダイアログにコピーしておいた送信先のtokenを貼り付けし`テスト`ボタンを押します。
![fcm18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315258/fb9ebe11-897d-e46a-2f30-ef80a52b8f18.png)

シミュレーターにメッセージが表示されれば成功です！
![fcm19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315258/f296b00b-0af4-11a2-13e1-11d70e0b4deb.png)

### Consoleからの送信テスト
curlが使えるconsoleからも送信テストができます。以下はmacでの例です。
`server key`は Fireaseの`設定`内、`Cloud Messaging`タブにある`サーバーキー`となります。
`project id`は Fireaseの`設定`内、`全般`タブにある`プロジェクトID`となります。
以下をconsoleにコピペし、`key=${server key}`などを`key=hogehoge`と置き換えて実行すれば送信されるかと思います。

```sh
curl -X POST \
--header "Authorization: key=${server key}" \
--header "project_id: key=${project id}" \
--header Content-Type:"application/json" \
https://fcm.googleapis.com/fcm/send \
-d @- << EOF
{
    "to": "${fcm token}",
    "notification": {
        "body": "curl test"
    },
}
EOF
```

正しく送信されると以下のようなResponseが戻り、シミュレーターにもメッセージが表示されます。

```
{"multicast_id":7325921480228621614,"success":1,"failure":0,"canonical_ids":0,"results":[{"message_id":"0:1607493418784266%a3c83467a3c83467"}]}
```

## 私がハマったポイント

* Xcodeの証明書に不慣れで`Automatically manage signing`について理解できていなかった
* APNs証明書で Keys（.p8） でよかったのに、がんばって .p12 を作ってしまった
* シミュレーターではPush通知の受け取りに制約があることを知らなかった
* 開発用証明書（Development）と本番証明書（Distribution）、AdHoc証明書、の違いがわかっていなかった
* iOSではアプリがバックグラウンド時にメッセージを受信しバックグラウンド処理を行うことが難しいことについてについて、しばらくしてから気づいた（今は簡単に出来るのかも？）

## まとめ
最低限のPush通知をFCMを利用して送信してみる内容でした。AndroidやiOS開発からFlutterに移動したかたは慣れていると思いますが、初めてのモバイルアプリ開発をFlutterで行う場合、結構な壁になるのかなぁとも思いました。少しでも参考になればと思います。
不明点や間違いなどありましたら、ご質問、ご指摘下さい！