Kotlin 01.2 の高度な Android: Android Firebase Cloud Messaging

この Codelab は、Kotlin での高度な Android 開発コースの一部です。Codelab を順番に進めていくと、このコースを最大限に活用できますが、これは必須ではありません。すべてのコース Codelab は Kotlin での Codelab の高度な Codelab のランディング ページに掲載されています。

はじめに

前の Codelab では、エッグタイマーに、アプリ内で作成されてトリガーされる通知を追加しました。通知のもう 1 つの重要な使用例は、アプリが実行されていないときでも受信できるプッシュ通知をリモートで送信することです。

プッシュ通知とは

プッシュ通知は、サーバーがモバイル デバイスにプッシュする通知です。アプリが動作しているかどうかにかかわらず、デバイスに配信できます。

プッシュ通知は、ユーザーに最新情報を知らせたり、タスクや機能を再認識させたりするための優れた手段です。商品の再入荷を待つことを想像してみてください。プッシュ通知を利用すれば、在庫状況を毎日確認しなくても、ショッピング アプリから最新の株価情報を確認できます。

プッシュ通知では、パブリッシュ / サブスクライブ パターンが使用されます。これにより、バックエンド アプリは関心のあるクライアントに関連コンテンツをプッシュできます。パブリッシュ/サブスクライブ モデルを使用しない場合、ユーザーはアプリのアップデートを定期的に確認する必要があります。このプロセスは繰り返しが多く、ユーザーにとっても信頼性がありません。さらに、クライアントの数が増えると、これらの定期的なチェックは、アプリのサーバーとユーザーのデバイスの両方にとって、ネットワーキング リソースと処理リソースに過大な負荷をかけることになります。

他のあらゆる通知と同様に、プッシュ通知でもユーザーに配慮することが重要です。通知コンテンツがユーザーにとって興味深く、タイムリーにならない場合、ユーザーはアプリからの通知をすべて簡単にオフにできます。

Firebase Cloud Messaging とは

Firebase Cloud Messaging はモバイル開発用の Firebase プラットフォームの一部です。通常は、モバイル デバイスと通信して通知をトリガーするサーバーをゼロからセットアップする必要があります。Firebase Cloud Messaging では、サーバーを設定せずに、インストール済みの全ユーザーまたはユーザーのサブセットに通知を送信できます。たとえば、リマインダーを送信したり、プレゼントなどの特別なプロモーションをユーザーに提示したりできます。通知を 1 台または複数のデバイスにリモートでプッシュすることもできます。

Firebase Cloud Message を使用して、バックエンド アプリまたは Firebase プロジェクトからユーザーにデータを転送することもできます。

この Codelab では、Firebase Cloud Messaging を使用して Android アプリのプッシュ通知を送信する方法と、データを送信する方法を学習します。

この Codelab で問題(コードのバグ、文法的な誤り、不明確な表現など)が見つかった場合は、Codelab の左下隅にある [誤りを報告] から問題を報告してください。

前提となる知識

以下について把握しておく必要があります。

  • Kotlin で Android アプリを作成する方法。特に、Android SDK を使用してください。
  • アーキテクチャ コンポーネントとデータ バインディングを使用してアプリを設計する方法。
  • ブロードキャスト レシーバに関する基礎知識。
  • AlarmManager に関する基本的な知識。
  • NotificationManager で通知を作成して送信する方法

学習内容

  • Firebase Cloud Messaging でメッセージをユーザーに push する方法
  • Firebase Cloud Messaging の一部であるデータ メッセージを使用して、バックエンドからアプリにデータを送信する方法。

演習内容

  • スターター アプリにプッシュ通知を追加します。
  • アプリの実行中に Firebase Cloud Messaging を処理する。
  • Firebase Cloud Messaging を使用してデータを転送する。

この Codelab では、Android アプリでの通知の使用に関する以前の Codelab のコードで演習を行います。前の Codelab では、調理タイマーが時間切れになったときに通知を送信する卵タイマーアプリを作成しました。この Codelab では、Firebase Cloud Messaging を追加して、卵を食べるようにリマインダーをアプリユーザーに送信します。

次のいずれかの方法でサンプルアプリを入手できます。

GitHub からリポジトリのクローンを作成し、starter ブランチに切り替えます。

$  git clone https://github.com/googlecodelabs/android-kotlin-notifications-fcm


リポジトリを ZIP ファイルとしてダウンロードして解凍し、Android Studio で開くこともできます。

ZIP をダウンロード

ステップ 1: Firebase プロジェクトを作成する

Android アプリに Firebase を追加する前に、Android アプリに接続するための Firebase プロジェクトを作成します。

  1. Firebase コンソールにログインします。
  2. [プロジェクトを追加] をクリックし、[プロジェクト名] を選択するか、新しいプロジェクト名を入力します。プロジェクトに fcm-codelab という名前を付けます。
  3. [続行] をクリックします。
  4. [このプロジェクトで Google アナリティクスを有効にする] をオフにすると、Google アナリティクスの設定をスキップできます。
  5. [プロジェクトを作成] をクリックして、Firebase プロジェクトの設定を完了します。

ステップ 2: アプリを Firebase に登録する

Firebase プロジェクトが作成されたので、Android アプリを追加できます。

  1. Firebase コンソールの [プロジェクトの概要] ページの中央にある Android アイコンをクリックして設定ワークフローを起動します。

  1. [Android パッケージ名] フィールドに「com.example.android.eggtimernotifications」と入力します。
  2. [アプリの登録] をクリックします。

重要: アプリに正しい ID を入力してください。この値は、アプリを Firebase プロジェクトに登録した後で追加、変更することはできません。

ステップ 3: プロジェクトに Firebase 構成ファイルを追加する

Firebase Android 構成ファイルをアプリに追加します。

  1. [google-services.json をダウンロード] をクリックして、Firebase Android 構成ファイル(google-services.json)を取得します。構成ファイルに文字が追加されていないことと、名前が google-services.json であることを確認してください。
  2. 構成ファイルをアプリのモジュール(アプリレベル)ディレクトリに移動します。

ステップ 4: Firebase プロダクトを有効にするように Android プロジェクトを構成する

アプリで Firebase プロダクトを有効にするには、Gradle ファイルに google-services プラグインを追加する必要があります。

  1. ルートレベル(プロジェクト レベル)の Gradle ファイル(build.gradle)に、Google の Maven リポジトリがあることを確認します。
  2. 次に、Google サービス プラグインを含めるためのルールを追加します。

build.gradle

buildscript {

  repositories {
    // Check that you have the following line (if not, add it):
    google()  // Google's Maven repository
  }

  dependencies {
    // ...

    // Add the following line:
    classpath 'com.google.gms:google-services:4.3.2'  // Google Services plugin
  }
}

allprojects {
  // ...

  repositories {
    // Check that you have the following line (if not, add it):
    google()  // Google's Maven repository
    // ...
  }
}
  1. モジュール(アプリレベル)の Gradle ファイル(通常は app/build.gradle)で、ファイルの最後にプラグインを適用するための行を追加します。

app/build.gradle

apply plugin: 'com.android.application'

android {
  // ...
}

// Add the following line to the bottom of the file:
apply plugin: 'com.google.gms.google-services'  // Google Play services Gradle plugin

このタスクでは、Firebase Cloud Messaging(FCM)をプロジェクトに追加して、プッシュ通知を活用します。

この Codelab の FCM の Android サービスコードは MyFirebaseMessagingService.kt にあります。次の手順で、Android アプリにコードを追加します。

Notifications Composer を使用して実装をテストします。Notifications Composer は、Firebase コンソールのウェブサイトからメッセージを作成して送信するのに役立つツールです。

  1. MyFirebaseMessagingService.kt を開く
  2. ファイル、特に次の関数を検査します。
  • onNewToken() - サービスが Android マニフェストに登録されている場合、自動的に呼び出されます。この関数は、初めてアプリを実行したとき、およびアプリの新しいトークンが Firebase に発行されるたびに呼び出されます。トークンは Firebase バックエンド プロジェクトのアクセスキーです。特定のクライアント デバイス用に生成されます。Firebase はこのトークンを使用して、バックエンドでメッセージの送信を行うクライアントを認識します。Firebase は、このクライアントが有効で、この Firebase プロジェクトにアクセスできるかどうかも認識します。
  • onMessageReceived - アプリが実行され、Firebase がアプリにメッセージを送信するときに呼び出されます。この関数は、通知またはデータ メッセージのペイロードを含む RemoteMessage オブジェクトを受け取ります。通知とデータ メッセージのペイロードの違いについては、この Codelab で後ほど詳しく学習します。

ステップ 1: 単一のデバイスに FCM 通知を送信する

Notifications コンソールを使用すると、通知の送信をテストできます。コンソールを使用して特定のデバイスにメッセージを送信するには、そのデバイスの登録トークンを知っておく必要があります。

Firebase バックエンドが新しいトークンまたは更新されたトークンを生成すると、onNewToken() 関数が呼び出され、新しいトークンが引数として渡されます。単一のデバイスを対象にする場合、またはブロードキャスト メッセージを送信するデバイスのグループを作成する場合は、FirebaseMessagingService を拡張して onNewToken() をオーバーライドして、このトークンにアクセスする必要があります。

  1. 卵タイマーアプリの MyFirebaseMessagingService を有効にするには、AndroidManifest.xml を開いて次のコードのコメントを解除します。Android マニフェストのサービスメタデータは MyFirebaseMessagingService をサービスとして登録し、このサービスが FCM から送信されたメッセージを受信できるようにインテント フィルタを追加します。メタデータの最後の部分で、breakfast_notification_channel_id が Firebase の default_notification_channel_id として宣言されています。この ID は次のステップで使用します。
<!-- AndroidManifest.xml -->
<!-- TODO: Step 3.0 uncomment to start the service  -->

        <service
                android:name=".MyFirebaseMessagingService"
                android:exported="false">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT"/>
            </intent-filter>
        </service>
        <!-- [START fcm_default_icon] -->
        <!--
 Set custom default icon. This is used when no icon is set for incoming notification messages.
             See README(https://goo.gl/l4GJaQ) for more.
        -->
        <meta-data
                android:name="com.google.firebase.messaging.default_notification_icon"
                android:resource="@drawable/common_google_signin_btn_icon_dark"/>
        <!--
 Set color used with incoming notification messages. This is used when no color is set for the incoming
             notification message. See README(https://goo.gl/6BKBk7) for more.
        -->
        <meta-data
                android:name="com.google.firebase.messaging.default_notification_color"
                android:resource="@color/colorAccent"/> <!-- [END fcm_default_icon] -->
        <!-- [START fcm_default_channel] -->
        <meta-data
            android:name="com.google.firebase.messaging.default_notification_channel_id"
            android:value="@string/breakfast_notification_channel_id" />
        <!-- [END fcm_default_channel] -->

ユーザーは卵タイマーや FCM プッシュ通知を個別に有効または無効にしたいので、FCM 用の新しい通知チャンネルを作成することをおすすめします。

  1. ui/EggTimerFragment.kt を開きます。onCreateView() に、次のチャンネル作成コードを追加します。
// EggTimerFragment.kt

   // TODO: Step 3.1 create a new channel for FCM
    createChannel(
        getString(R.string.breakfast_notification_channel_id),
        getString(R.string.breakfast_notification_channel_name)
    )
  1. MyFirebaseMessagingService.kt を開き、onNewToken() 関数のコメント化を解除します。この関数は、新しいトークンが生成されるときに呼び出されます。
// MyFirebaseMessagingService.kt

   // TODO: Step 3.2 log registration token
    // [START on_new_token]
    /**
     * Called if InstanceID token is updated. This may occur if the security of
     * the previous token had been compromised. Note that this is called when the     
     * InstanceID token is initially generated so this is where you would retrieve     
     * the token.
     */
    override fun onNewToken(token: String?) {
        Log.d(TAG, "Refreshed token: $token")

        // If you want to send messages to this application instance or
        // manage this apps subscriptions on the server side, send the
        // Instance ID token to your app server.
        sendRegistrationToServer(token)
    }
    // [END on_new_token]
  1. 卵タイマーアプリを実行します。
  2. logcat を確認します(表示 > ツール Windows > logcat)。トークンが以下のようなログ行に表示されます。このデバイスにメッセージを送信するために必要なトークンです。この関数は、新しいトークンが作成された場合にのみ呼び出されます。
2019-07-23 13:09:15.243 2312-2459/com.example.android.eggtimernotifications D/MyFirebaseMsgService: Refreshed token: f2esflBoQbI:APA91bFMzNNFaIskjr6KIV4zKjnPA4hxekmrtbrtba2aDbh593WQnm11ed54Mv6MZ9Yeerver7pzgwfKx7R9BHFffLBItLEgPvrtF0TtX9ToCrXZ5y7Hd-m

注: logcat メッセージにトークンが表示されない場合は、アプリがすでにトークンを受け取っている可能性があります。その場合は、このアプリをアンインストールすると、新しいトークンを受け取ることができます。

通知を送信してテストできるようになりました。通知を送信するには、Notifications Composer を使用します。

  1. Firebase コンソールを開き、プロジェクトを選択します。
  2. 左側のナビゲーションで [Cloud Messaging] を選択します。
  3. [最初のメッセージを送信] をクリックします。

  1. 通知タイトルとして「Time for Breakfast!」、通知テキストに「Don't forget to eat eggs!」と入力し、[テスト メッセージを送信] を選択します。[Test on device] ポップアップ ダイアログが表示され、FCM 登録トークンを入力するよう求められます。

  1. logcat からアプリトークンをコピーします。

  1. ポップアップ ウィンドウ内の [FCM 登録トークンを追加] フィールドにこのトークンを貼り付け、トークンの横にある [追加] ボタンをクリックします。
  2. 表示されたチェックボックス リストでトークンを選択します。[テスト] ボタンが有効になります。

  1. デバイス上で、エッグタイマー アプリをバックグラウンドに表示できます。
  2. ポップアップで [テスト] をクリックします。
  1. [テスト] をクリックすると、アプリをバックグラウンドで実行しているターゲット クライアント デバイスのシステム通知トレイに通知が届きます。(アプリがフォアグラウンドにある場合に FCM メッセージを処理する方法について詳しくは、後ほど説明します)。

タスク: FCM 通知をトピックに送信する

FCM トピック メッセージングは、パブリッシュ/サブスクライブ モデルに基づいています。

メッセージ アプリは、パブリッシュ / サブスクライブ モデルの良い例です。あるアプリが 10 秒ごとに新しいメッセージをチェックするとします。この場合、スマートフォンのバッテリーが消耗するだけでなく、不要なネットワーク リソースが使用され、アプリのサーバーに不要な負荷がかかることになります。クライアント デバイスは、アプリを介して配信され、新しいメッセージが配信されたときに通知を受け取ることができます。

トピックを使用すると、そのトピックにオプトインした複数のデバイスにメッセージを送信できます。クライアントの場合、トピックはクライアントが関心のあるデータソースです。サーバーの場合、トピックとは、特定のデータソースに関する最新情報の受信を選択しているデバイスのグループのことです。トピックを使用して通知のカテゴリ(ニュース、天気予報、スポーツの結果など)を表示できます。Codelab のこのパートでは、「朝食」というトピックを作成して、関心のあるユーザーがアプリの朝食に卵を食べるように促します。

トピックにサブスクライブするために、クライアント アプリは、トピック名 breakfast を使用して Firebase Cloud Messaging subscribeToTopic() 関数を呼び出します。この呼び出しの結果は 2 つあります。呼び出し元が成功すると、登録されたメッセージとともに OnCompleteListener コールバックが呼び出されます。クライアントがサブスクライブできない場合は、代わりにコールバックにエラー メッセージが表示されます。

アプリでは、ユーザーを朝食トピックに自動的に登録します。ただし、ほとんどの製品版アプリでは、登録するトピックをユーザーが制御できるようにすることをおすすめします。

  1. EggTimerFragment.kt を開き、空の subscribeTopic() 関数を探します。
  2. FirebaseMessaging のインスタンスを取得し、トピック名を指定して subscibeToTopic() 関数を呼び出します。
  3. addOnCompleteListener を追加して、サブスクリプションが成功したか失敗したかについて FCM から通知を受け取ります。
// EggTimerFragment.kt

   // TODO: Step 3.3 subscribe to breakfast topic
    private fun subscribeTopic() {
        // [START subscribe_topics]
        FirebaseMessaging.getInstance().subscribeToTopic(TOPIC)
            .addOnCompleteListener { task ->
                var msg = getString(R.string.message_subscribed)
                if (!task.isSuccessful) {
                    msg = getString(R.string.message_subscribe_failed)
                }
                Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
            }
        // [END subscribe_topics]
    }
  1. アプリの起動時にトピックに登録するには、subscribeTopic() 関数を呼び出します。onCreateView() まで上にスクロールして、subscribeTopic() への通話を追加します。
// EggTimerFragment.kt

   // TODO: Step 3.4 call subscribe topics on start
    subscribeTopic()

    return binding.root
  1. 朝食のトピックを登録するには、アプリをもう一度実行してください。「トピックにサブスクライブしました」というトースト メッセージが表示されます。

これで、トピックへのメッセージの送信をテストできます。

  1. Notifications Composer を開き、[通知を作成] を選択します。
  2. これまでと同様に、通知通知タイトル通知テキストを設定します。
  3. 今回は、1 台のデバイスにメッセージを送信する代わりに、[Target] の下の [Topic] をクリックし、メッセージのトピックとして「breakfast」と入力します。

  1. [スケジューリング] で [現在] を選択します。

  1. アプリがテストデバイスでバックグラウンドで実行されていることを確認します。
  1. [Review] をクリックし、[Publish] をクリックします。複数のデバイスでアプリを実行できれば、このトピックに登録されたすべてのデバイスで通知を受信してテストできます。

アプリに通知チャンネルとして利用できるようになりました。EggBreakfast です。クライアント デバイスでアプリアイコンを長押しして [情報] を選択し、[通知] をクリックします。次のスクリーンショットに示すように、朝食の通知チャンネルが表示されます。[Breakfast] チャンネルの選択を解除した場合、そのチャンネル経由で送られた通知は送信されません。

通知を使用する際は、ユーザーがいつでも通知チャンネルをオフにできることにご注意ください。

ステップ 1: データ メッセージ

FCM メッセージには、クライアント アプリ内のメッセージを処理するデータ ペイロードを含めることができ、通知メッセージの代わりにデータ メッセージを使用できます。

データ メッセージを処理するには、MyFirebaseMessagingServiceonMessageReceived() 関数でデータ ペイロードを処理する必要があります。ペイロードは、remoteMessage オブジェクトの data プロパティに格納されます。remoteMessage オブジェクトと data プロパティはどちらも null にできます。

  1. MyFirebaseMessagingService. を開きます。
  2. remoteMessage オブジェクトの data プロパティに値があるかどうかを確認し、データをログに出力します。
// MyFirebaseMessagingService.kt

    // [START receive_message]
    override fun onMessageReceived(remoteMessage: RemoteMessage?) {
        // Not getting messages here? See why this may be: https://goo.gl/39bRNJ
        Log.d(TAG, "From: ${remoteMessage?.from}")
        
       // TODO: Step 3.5 check messages for data
        // Check if the message contains a data payload.
        remoteMessage?.data?.let {
            Log.d(TAG, "Message data payload: " + remoteMessage.data)
        }

    }
    // [END receive_message]

コードをテストするには、もう一度 Notifications Composer を使用します。

  1. Notification Composer を開き、新しいメッセージを作成し、[Target] をトピック「breakfast」に設定します。
  2. ステップ 4 の [Additional options] に到達したら、[Custom data] キーと値のプロパティを次のように設定します。
  1. キー: eggs
  2. 値: 3

  1. アプリがフォアグラウンドで実行されていることを確認します。アプリがバックグラウンドで動作している場合、FCM メッセージによって自動通知がトリガーされ、onMessageReceived() 関数はユーザーが通知をクリックしたときに remoteMessage オブジェクトのみを受け取ります。
  2. Notifications Composer からメッセージを送信し、logcat に表示されるデータ メッセージログを確認します。

ステップ 2: フォアグラウンドとバックグラウンドでメッセージを処理する

アプリを実行しているクライアント デバイスが、メッセージとデータ ペイロードの両方を含むメッセージを受信した場合、アプリの動作は、そのアプリがバックグラウンドであるかフォアグラウンドであるかによって異なります。

  • アプリがバックグラウンドで動作している場合に、メッセージに通知ペイロードがある場合、通知は通知トレイに自動的に表示されます。メッセージにデータ ペイロードがある場合、ユーザーが通知をタップするとデータ ペイロードがアプリで処理されます。
  • アプリがフォアグラウンドで実行されているとき、メッセージ通知に通知ペイロードがある場合、通知は自動的には表示されません。アプリは、onMessageReceived() 関数で通知を処理する方法を決定する必要があります。メッセージにデータ ペイロードがある場合、両方のペイロードはアプリで処理されます。

この Codelab では、朝食用の卵を何個か用意するようアプリユーザーに伝えます。データを送信する予定はないが、アプリがフォアグラウンドで動作しているかバックグラウンドであるかにかかわらず、常にリマインダー通知が表示されることを確認する。

卵タイマーアプリがインストールされているデバイスに FCM メッセージを送信すると、アプリが実行されていないかバックグラウンドで実行されている場合、通知メッセージが自動的に表示されます。ただし、アプリがフォアグラウンドで実行されている場合、通知は自動的には表示されません。代わりに、アプリのコードがメッセージの処理方法を決定します。アプリが FCM メッセージを受信したときにフォアグラウンドにある場合、onMessageReceived() 関数は、FCM メッセージで自動的にトリガーされます。ここで、通知やデータのペイロードを通知せずに処理したり、通知をトリガーしたりすることができます。

アプリの場合は、アプリがフォアグラウンドのときにユーザーにリマインダーが表示されるようにするため、通知をトリガーするコードを実装してみましょう。

  1. MyFirebaseMessagingServiceonMessageReceived() 関数を再度開きます。
  2. データ メッセージをチェックするために最近追加したコードの直後に、通知フレームワークを使用して通知を送信する次のコードを追加します。
// MyFirebaseMessagingService.kt

    // TODO: Step 3.6 check messages for notification and call sendNotification
    // Check if the message contains a notification payload.
    remoteMessage.notification?.let {
        Log.d(TAG, "Message Notification Body: ${it.body}")
        sendNotification(it.body as String)
    }
  1. アプリを再度実行し、Notifications Composer を使用して通知を送信すると、アプリがフォアグラウンドにあるかバックグラウンドであるかにかかわらず、Codelab の前半で見たのと同じように通知が表示されます。

解答コードは、ダウンロードしたコードのマスター ブランチにあります。

  • FirebaseMessagingService を拡張して FCM BroadcastReceiver を実装する。
  • Firebase Cloud Messaging(FCM)プロジェクトを設定し、Android アプリに FCM を追加します。
  • Notifications Composer からプッシュ通知を送信してアプリをテストします。
  • FCM トピックにサブスクライブするには、FirebaseMessaging クラスの subscribeToTopic() 関数を呼び出します。
  • RemoteMessage オブジェクトを使用してデータ ペイロードを送信します。
  • onMessageReceived() 関数でデータを処理します。
  • アプリがフォアグラウンドのときとバックグラウンドのときに FCM を処理するロジックを追加します。

Udacity コース:

Firebase ドキュメント:

このコースの他の Codelab へのリンクについては、Kotlin での高度な Android Codelab のランディング ページをご覧ください。