Goalist Developers Blog

Firebase(FCM)を用いたAndroid(Kotlin)でのプッシュ通知

こんにちは。
増田です。

FirebaseのCloud Messagingを利用してAndroidでプッシュ通知を実装したくなった、
というよりしないといけなくなったのでそのまとめを。

ただ、Androidを触ったこともなければKotlinでコードを書いたこともない状態からの
いきなりのプッシュ通知の実装だったので、よく分かっていない部分が多いですが、
多少なりともご参考になれば幸いです。

通知が表示されるようになるまでに割と時間がかかってしまったのですが、
それもこれも全部全然関係のないところに出ていたエラーのせいでした。
エラーが出ている場合はまずそこを潰しましょう。
基本中の基本なのかもしれませんが、僕は新人なので()

プロジェクト作成

プロジェクトがないと始まらないので、空っぽのプロジェクトを作ります。
Android Studioを使って、適当に。
ここは他の方の良い記事 がありますのでさっくり割愛。

というわけで、空っぽのプロジェクトができました。

プロジェクトにFirebaseを追加

公式の資料がありますので、そちらを。
とはいえ省きすぎるのもどうかと思うので、資料に書いてあることそのままにはなりますが、
この辺からちょこっと丁寧に。

まず、Firebaseプロジェクトがなければ作ります。
Firebaseコンソールに行き、

f:id:s-masuda:20181009124728p:plain

を押して、適当にプロジェクト名を決めて、

f:id:s-masuda:20181009124758p:plain

同意して、プロジェクトを作成。
その後、作ったプロジェクトに追加すべく、

f:id:s-masuda:20181009124859p:plain

のAndroidのアイコンを押します。
そして、必要な情報を入力していきます。

f:id:s-masuda:20181009124949p:plain

とは言っても今回はパッケージ名だけで大丈夫みたいです。
パッケージ名というのは、作ったプロジェクトのAndroidManifest.xmlにある

f:id:s-masuda:20181009125112p:plain

このpackageのことです。
で、「アプリを登録」ボタンを押すと、

f:id:s-masuda:20181009125203p:plain

こんな感じの画面になるので、書かれてある通りにgoogle-services.jsonをダウンロードして、
appの下に入れます。そして「次へ」ボタンを押す前に、ちょこっとコードを書いておきます。

ルートレベルのbuild.gradleに

buildscript {
    // ...
    dependencies {
        // ...
        classpath 'com.google.gms:google-services:4.1.0' // google-services plugin    <----コレ
    }
}

を追記します。
ルートレベルのbuild.gradleというのは、Android Studioで見たときに

f:id:s-masuda:20181009125802p:plain

のようにして見える上の方のbuild.gradleです。
で、モジュールのbuild.gradle(画像での下の方のbuild.gradle)に

dependencies {
  // ...
  implementation 'com.google.firebase:firebase-core:16.0.3'

  // Getting a "Could not find" error? Make sure you have
  // added the Google maven respository to your root build.gradle
}

// ADD THIS AT THE BOTTOM
apply plugin: 'com.google.gms.google-services'

を追記します。最後の行の apply plugin: 〜はbuild.gradleの最後(一番下)に書く必要があるらしいです。コメントにも注意書きがありますし。

で、ここまでで準備が整ったので、さっき飛ばした「次へ」ボタンを押します。
すると、おそらく

f:id:s-masuda:20181009130917p:plain

というのが表示されるので、アプリを実行します。 そうしてしばらく経つと、

f:id:s-masuda:20181009130947p:plain

というメッセージが表示されるのではないかと。
ここまできたらFirebaseをプロジェクトに追加できたということみたいです。

Firebase Cloud Messagingを利用できるように

次は、Cloud Messaging を利用できるようにします。
モジュールのbuild.gradleに

dependencies {
    compile 'com.google.firebase:firebase-messaging:17.3.1'
}

を追記します。これだけです。
資料ではバージョンが12.0.1になっているのですが、それだとおそらく

f:id:s-masuda:20181009133047p:plain

というようなエラーがずらーっと出てきます。
なのでバージョンをちょこっと変えています。

バックグラウンドで通知メッセージを受け取った場合の設定

バックグラウンド時の通知メッセージ(notification)限定です。
詳しいことは公式の資料を見ていただければ。

プッシュ通知用の諸々のデフォルト設定をAndroidManifest.xmlに追加します。
追加する箇所は<application 〜>の後ろくらいに。
公式のクイックスタートがありますので、そちらをご参考いただければ。

<meta-data
    android:name="com.google.firebase.messaging.default_notification_channel_id"
    android:value="@string/default_notification_channel_id"/>
<!-- 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/ic_launcher_foreground" />
<!-- 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" />

別にコメントは必要ありませんけど、一応。
あと、@string/default_notification_channel_idがないと言われるかと思いますので、
strings.xmlに追加します。

<string name="default_notification_channel_id" translatable="false">my_app_fcm_default_channel</string>

channel_idで指定されている文字列が一体どういうものを指しているのか、
どういうところで利用されているのか等々一切理解していません。
とにかく何か適当な文字列を入れておけば動くみたいなので...。

これで準備はできました。

メッセージ送信

というわけで試しにメッセージを送信してみます。
Firebaseコンソールに行き、左側の「拡大」という部分にあるCloud Messagingというところに飛んで

f:id:s-masuda:20181009141256p:plain

こんな感じで、

f:id:s-masuda:20181009141425p:plain

メッセージ本文とターゲットを指定して「メッセージを送信」ボタンを押すだけです。
今回面倒なのでとりあえずターゲットはアプリにしています。

はい。送信しました。
この時点でもしLogcatによく分からないエラーが出ていたら、
エミュレーターをCold Bootしてみると良いかもしれません。

すると、

f:id:s-masuda:20181009174124p:plain

はい。ハッピーです。 正直、お仕事はここまでで十分なのですが、
一応その通知を細かくカスタマイズできるようになるところまで書いておきます。

フォアグラウンド時で受け取った場合などの設定

バックグラウンド時にシンプルな通知メッセージを受け取った場合は、
上記の設定だけで十分なのですが、もしそうでない場合、
例えばアプリがフォアグラウンド時にも通知を出したいというような場合には、
ちょこっとコードを書く必要があります。

まず、どこで受け取るのかというのをAndroidManifest.xmlで指定しておきます。

<service
    android:name=".services.FirebaseNotificationService">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT"/>
    </intent-filter>
 </service>

.services.FirebaseNotificationServiceというのは今から作ります。

メッセージ受信時のメソッドを実装

というわけで、com.example.〜の下にservicesというパッケージを作成し、
その中にFirebaseNotificationServiceというクラスを作ります。名前は適当です。
そしてそこに通知を受け取った際のメソッドを実装します。

class FirebaseNotificationService : FirebaseMessagingService() {
    // プッシュ通知を受け取った際の処理
    override fun onMessageReceived(remoteMessage: RemoteMessage?) {

        // Check if message contains a data payload.
        remoteMessage?.data?.isNotEmpty()?.let {
            Log.d(TAG, "Message data payload: " + remoteMessage.data)
        }

        // Check if message contains a notification payload.
        remoteMessage?.notification?.let {
            Log.d(TAG, "Message Notification Body: ${it.body}")
        }

        this.sendNotification()
    }

    // 新しいTokenが作成された際の処理
    override fun onNewToken(token: String?) {
        // サーバーに送信するなど
    }

    /**
     * Create and show a simple notification containing the received FCM message.
     *
     */
    private fun sendNotification() {
        val intent = Intent(this, MainActivity::class.java)
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
        val pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
                PendingIntent.FLAG_ONE_SHOT)

        val channelId = getString(R.string.default_notification_channel_id)
        val channelName = getString(R.string.default_notification_channel_name)
        val notificationBuilder = NotificationCompat.Builder(this, channelId)
                .setSmallIcon(R.drawable.ic_launcher_foreground) // 必須
                .setColor(Color.parseColor("#00ABA9")) // theme colorを文字列指定(リソースでの指定方法が分からなかったので)
                .setContentTitle("タイトル")
                .setContentText("コンテンツ")
                .setShowWhen(true)
                .setWhen(System.currentTimeMillis())
                .setAutoCancel(true)
                .setDefaults(NotificationCompat.DEFAULT_ALL)
                .setPriority(NotificationCompat.PRIORITY_MAX)
                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
                .setContentIntent(pendingIntent)

        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        // Since android Oreo notification channel is needed.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH)
            channel.description = "description" // どこに使われるものなのか分かっていません
            channel.setShowBadge(true)
            channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
            notificationManager.createNotificationChannel(channel)
        }

        notificationManager.notify(0 /* ID of notification */, notificationBuilder.build())
    }

    companion object {
        private var TAG = "FirebaseMessagingService"
    }
}

さらっとR.string.default_notification_channel_nameというのを入れたのですが、
もちろんそれに対応するものをstrings.xmlに追記する必要があります。

<string name="default_notification_channel_name" translatable="true">my_app_push_message</string>

あと、一点注意しないといけないのは、資料では

<service
    android:name=".MyFirebaseInstanceIDService">
    <intent-filter>
        <action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
    </intent-filter>
</service>

現在のトークンを取得する必要がある場合は、FirebaseInstanceId.getInstance().getToken() を呼び出します。

というような記述がある(2018/10/09)のですが、
com.google.firebase.INSTANCE_ID_EVENT.getToken()も非推奨になっているらしいです。
参考ページ

これで一応全ての準備が整いました。
なので、フォアグラウンド状態でFirebaseコンソールからメッセージを送ってみます。

f:id:s-masuda:20181009175252p:plain

うい。
めでたしめでたし。
満足です。