AndroidアプリにFirebase Cloud Messagingを実装する(2) - アプリでメッセージを受信する

Firebase Cloud Messagingを使って、Androidアプリにプッシュ通知を導入する方法について書きました。
このエントリでは実際に通知を受信するのに必要なコードを書いて、通知を受信するまでをまとめています。

# FCMメッセージの種類
FCMメッセージの種類には以下の2つがあります。詳しくは[こちら](https://firebase.google.com/docs/cloud-messaging/concept-options#notifications_and_data_messages)をご覧ください。

| メッセージタイプ | 概要 |
| --- | --- |
| 通知メッセージ | アプリの代わりにFCMが処理するメッセージ。予め定義されたキーを使って送信するため、決められた以外の情報を送ることはできない |
| データメッセージ | アプリが直接処理するメッセージ。メッセージのキーなど、送信側が自由にメッセージのフォーマットを決めることができる |

両者の違いは主に以下の2つです。

* メッセージ送信に使うJSONのフォーマットが違う
* アプリでメッセージを受信した時の挙動が異なる

メッセージを受信した時のアプリの挙動は以下の通りです。
"onMessageReceived"と書かれているケースでは、[FirebaseMessagingServiceクラスを継承したサービスのonMessageReceivedメソッド](https://firebase.google.com/docs/reference/android/com/google/firebase/messaging/FirebaseMessagingService.html#onMessageReceived(com.google.firebase.messaging.RemoteMessage))が呼び出されます。

| メッセージタイプ | アプリがフォアグラウンド | アプリがバックグラウンド |
| --- | --- | --- |
| 通知メッセージ | タスクトレイに入る |　onMessageReceived |
| データメッセージ | onMessageReceived |　onMessageReceived |

通知メッセージにはデータを含めることもできます。両者を併用した場合については[ここ](https://firebase.google.com/docs/cloud-messaging/android/receive)を参考にしてください。

# 前提条件
AndroidプロジェクトにFirebaseが追加されていること。追加する方法は、[前のエントリ](https://qiita.com/outerlet/items/e3fe96a3d1e15ed42573)をご確認ください。

# 環境
以下の環境を使って実施しています。

* macOS Mojave バージョン10.14.5
* Android Studio 3.4.1
* Nexus5X + Android7.1.2
* Pixel3a + Android9

# 実装

## サービスの定義
[FirebaseMessagingService](https://firebase.google.com/docs/reference/android/com/google/firebase/messaging/FirebaseMessagingService)を継承したサービスを作成します。
このサービスには、以下2つのメソッドを実装します（他にも実装できるメソッドはありますが、ここでは割愛します）。

| メソッド | 呼び出されるタイミング |
| --- | --- |
| onNewToken | トークン（端末＋アプリの組み合わせを一意に識別するための文字列）が更新された |
| onMessageReceived | 通知を受信した |

それぞれのメソッドを実装してみました。本来ならトークンが更新されたらサーバに保存するなどの処理が必要ですが、ここではログに出すだけにしています。

```Java:MyFirebaseMessagingService.java
public class MyFirebaseMessagingService extends FirebaseMessagingService {
    @Override
    public void onNewToken(String token) {
        // 端末＋アプリを一意に識別するためのトークンを取得
        Log.i("FIREBASE", "[SERVICE] Token = " + token);
    }

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        if (remoteMessage != null) {
            // 通知メッセージ
            RemoteMessage.Notification notification = remoteMessage.getNotification();
            if (notification != null) {
                // 通知メッセージを処理
            }

            // データメッセージ
            Map<String, String> data = remoteMessage.getData();
            if (data != null) {
                // データメッセージを処理
            }
        }
    }
}
```

```Kotlin:MyFirebaseMessagingService.kt
class MyFirebaseMessagingService : FirebaseMessagingService() {
    override fun onNewToken(token: String?) {
        // 端末＋アプリを一意に識別するためのトークンを取得
        Log.i("FIREBASE", "[SERVICE] Token = ${token ?: "Empty"}")
    }

    override fun onMessageReceived(remoteMessage: RemoteMessage?) {
        remoteMessage?.let { message ->
            // 通知メッセージ
            message.notification?.let {
                // 通知メッセージを処理
            }

            // データメッセージ
            message.data?.let {
                // データメッセージを処理
            }
        }
    }
}
```

作成したサービスはAndroidManifest.xmlに登録しておきます。

```XML:AndroidManifest.xml
<service
        android:name=".MyFirebaseMessagingService"
        android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT"/>
    </intent-filter>
</service>
```

## 最新のトークンを確認
「端末登録トークン」（以下「トークン」）は、端末＋アプリの組み合わせを一意に識別するための文字列です。これを利用してアプリにメッセージを送信します。
トークンは、**ユーザーの事情やサーバ側の都合で更新される可能性があります**。常に最新のトークンを取得することが推奨されていますので、アプリ起動時に確認するよう実装するのが適当かと思います。
トークンを取得するためのコードは以下の通りです。

```Java:トークンの取得(Java)
FirebaseInstanceId.getInstance().getInstanceId().addOnCompleteListener((task) -> {
    if (!task.isSuccessful()) {
        Log.w("FIREBASE", "getInstanceId failed.", task.getException());
        return;
    }

    String token = (task.getResult() == null) ? "empty" : task.getResult().getToken();
    Log.i("FIREBASE", "[CALLBACK] Token = " + token);
});
```

```Kotlin:トークンの取得(Kotlin)
FirebaseInstanceId.getInstance().instanceId.addOnCompleteListener { task ->
    if (!task.isSuccessful) {
        Log.w("FIREBASE", "getInstanceId failed", task.exception)
        return@addOnCompleteListener
    }

    Log.i("FIREBASE", "[CALLBACK] Token = ${task.result?.token}")
}
```

## FCMメッセージの受信
メッセージを受信すると、FirebaseMessagingServiceのサブクラスに実装したonMessageReceived(RemoteMessage)が呼び出されます。その中に、メッセージをパースしてどのように扱うかのロジックを定義します。
『サービスの定義』の項に載せたサンプルコードは、通知メッセージとデータメッセージそれぞれに対応しています。ご確認ください。

## FCMメッセージの送信
ここまでできればメッセージを受信する準備は完了です。
Firebaseコンソール、もしくはHTTPリクエストでメッセージを送信することができますので試してみます。

### a.コンソールから送信
通知メッセージを送るには、Firebaseコンソールにログインして目的のプロジェクトを開き、「Cloud Messaging」へ遷移します。
『Send your first message』をクリックします。
<img width="640" alt="fcm1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67520/bd5e7acb-75b9-71ea-c8dd-c36a1eb7934a.png">

* 「通知のタイトル」「通知テキスト」には適当な文字列を入力してください。
* 「ターゲット」にはアプリケーションIDを入力してください。

2回目以降であれば、今まで送信したメッセージが一覧表示されています。
画面左上の『新しい通知』をクリックして、新しいメッセージを送信します。既存のメッセージを複製することもできます。
<img width="640" alt="fcm2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67520/ce469c34-f75c-2fa1-cf26-aa5f77a4e16e.png">

### b.cURLを使って送信
次に、cURLを使って通知メッセージとデータメッセージの両方を送信します。cURLの導入や使用の方法については割愛します。

サーバキーを確認します。
Firebaseコンソールで歯車型の設定アイコン①をクリックして画面を開きます。設定画面では『クラウド　メッセージング』タブ②をクリックしてください。『サーバーキー』③が表示されますのでコピーしておいてください。
<img width="960" alt="fcm27.png" src="https://qiita-image-store.s3.amazonaws.com/0/67520/f66ea863-255a-3814-f776-63a763bbeb0a.png">

以下はデータメッセージを送信するコマンドの例です。

```bash
SERVER_KEY=（サーバーキー）
TARGET_TOKEN=（トークン）

curl -X POST -H "Content-Type:application/json" \
     -H "Authorization:key=$SERVER_KEY" \
     -d "{\"data\":{\"name\" : \"Taro Nihon\",\"age\" : \"20\",\"address\" : \"Tokyo\"},\"to\" : \"$TARGET_TOKEN\"}" \
     https://fcm.googleapis.com/fcm/send
```

# まとめ
コードの作成からメッセージを受信するまでを書いてみました。
ここでは最低限の内容でFCMメッセージを作成していますが、有効期限や優先順位など、他にも色々な情報を含めることができます。
以下を参考に、いろんな実装を試してみてください。

* 通知メッセージとデータメッセージの違いなど、FCMメッセージの詳細については[こちら](https://firebase.google.com/docs/cloud-messaging/concept-options)で確認してください。
* 送信時のJSONに定義することのできる内容は、[リファレンス](https://firebase.google.com/docs/cloud-messaging/http-server-ref)で確認できます。


# 改訂履歴

* 2019/05/24
    * 動作・検証環境を変更しました。
    * Javaのコードを修正しました。
    * Kotlinのコードを追加しました。
    * FirebaseInstanceIdServiceがdeprecatedになっていたため、記述を削りました。
    * スクリーンショットを全て撮り直しました。