RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのITエンジニアによる技術ブログです。

Firebaseでローコードなプッシュ通知を実装してみた

f:id:hyosh:20210325172737p:plain こんにちはラクス開発エンジニアのhyoshです。 今回は久しぶりのFirebaseシリーズを投稿いたします。

過去Firebaseの多様なソリューションからチャット(RealTime Database)、認証(Authentication)とご紹介してきましたが今回はプッシュ通知(Firebase Cloud Messaging)に携わる機会がありましたのでご紹介させていただきます。

過去の投稿はよろしければ以下もご覧ください。 tech-blog.rakus.co.jp tech-blog.rakus.co.jp

Firebase Cloud Messagingについて

できること

Firebase Cloud Messaging(以降FCM)はFirebaseのソリューションの一つで、利用することでプッシュ通知を簡単に実現できます。 下図は公式ガイドに記載されているFCMアーキテクチャですが、プッシュ通知は主にメッセージの作成(1)、転送(2,3)、受信(4)という流れで処理されます。

f:id:hyosh:20210317184611p:plain
例えば特定タイミングでユーザーにキャンペーン情報を通知したいといったケースを考えた際、これらを全て自前で実装するとなるとかなり敷居が高くなる事は想像がつくでしょう。

一方でFCMを用いれば根幹となる1,2,3をバックエンドでFCMが実施してくれるため、開発者に必要なのはクライアントの軽微な実装のみ(※1)となり複雑な処理の中身を意識する必要がありません。

※1…実際の開発現場では後述のように1に関しても個別実装するケースが多いかと思いますが、それでもFCMを利用することで自前で行うよりずっと簡易に行えます。

利用することによるメリット

FCMを利用してプッシュ通知を実現するメリットですが、私は以下の点を感じています。

  1. バックエンド処理の実装が不要
  2. OSの違いを吸収してくれる(クロスプラットフォーム対応)
  3. 導入・運用コストが低く経済的

1は先にお話した通りで、プッシュ通知を実現するにあたっての煩雑な処理は全てFCMが担当してくれるので、開発者はクライアント実装のみに注力すればよくなります。

2はFCM公式でも強く打ち出している特徴で、iOSであってもAndroidであってもメッセージの作成、転送までは同じ処理を共有できるいわゆるクロスプラットフォームな開発が行えます。
各端末のアプリで実施される受信処理はそれぞれのOSで実装は必要ですが、それでも各OS毎に行う作業はグッと減らせます。

そして3の特徴ですが、これだけ充実したサポートを誇っていながらFCMを利用することでかかるコストは下記の通り無料です。 firebase.google.com よくありがちな特定量から課金という事もなく、何通送っても0円です(※2)。

後ほどサンプルで紹介もしますが、導入も非常に容易なのでありがたい限りですね。

これらのことから現在プッシュ通知実装に関し、まずFCMの名前が出てくるのも納得の理由かと感じていただけるかと思います。

※2…2021年3月時点の情報であり、今後変更となる可能性はあります。

サンプル実装

FCMがバックエンドを担当してくれると言っても実態がイメージしづらいと思うので、ここからは実際にAndroidで簡単な通知を受け取れるサンプルアプリを作っていきたいと思います。

公式ガイドをベースとしているため、合わせて参照ください。

Firebaseプロジェクトの作成

まずは表示されているガイドに沿ってFirebaseにてプロジェクトを作成していきます。

1.一意となる任意のプロジェクト名称を入力します。 f:id:hyosh:20210317201123p:plain 2.Googleアナリティクスを利用することでより効果的にFirebaseのソリューションを使うことができますが、ここではひとまず利用しないでプロジェクトを作成します。 f:id:hyosh:20210317202042p:plain これでプロジェクト作成は完了となり、コンソール画面が表示されます。

FCMをアプリに登録

続いて作成したプロジェクトをアプリに登録し、互いをひもづけます。

1.コンソールの左メニューよりCloud Messagingをクリックし、Androidアイコンを押して設定画面に進みます。 f:id:hyosh:20210317204009p:plain 設定は親切なガイドが表示されているので、それに従って進めていきます。

クライアントの実装

ここまででFCM側の作業は完了したので、次に通知を受信するためのクライアント側の実装を行っていきます。

1.FCMではトークンという情報を用いて端末を一意に識別します。
トークンの実態は端末とアプリの組み合わせで一意となる文字列情報となり、これをFCMで認知できている事で狙った端末に通知が送れるという仕組みになっています。もちろん重要情報なので取扱いには重々注意が必要となります。

トークンを発行するサービスがFCMから提供されているので次のように実装していきます。

// build.gradle(:app)にFCMライブラリを追加
dependencies {
    implementation platform('com.google.firebase:firebase-bom:26.7.0')
    implementation 'com.google.firebase:firebase-messaging-ktx' // この一行を追加
}
package com.example.notifyapplication

import android.util.Log
import com.google.firebase.messaging.FirebaseMessagingService

class MyMessageService : FirebaseMessagingService() {
    override fun onNewToken(token: String) {
        Log.d("MyMessageService", "Refreshed token: $token")
    }
}

新規にFirebaseMessagingServiceを継承したクラスを作成します。 onNewTokenはFirebaseMessagingServiceに用意されているメソッドでアプリがトークンを発行するためにアプリ起動時に呼び出されます。

ただし毎回呼び出される訳ではなくまだ一度もトークンを発行していない、何らかの要因でトークンを更新する必要がある(※3)といったケースで限定的に呼び出されます。

ここまでできたら一度アプリを動かしてみましょう。 ログにトークンが出力されているかと思います。

表示されていない場合は既に発行された後の可能性があります。 そのような場合に備えてガイドでは現在のトークンを取得する方法も開示されているので、以下のようにMainActivityに追加してみましょう。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    setSupportActionBar(findViewById(R.id.toolbar))

    // ここから追加
    FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
        if (!task.isSuccessful) {
            Log.w("MainActivity", "Fetching FCM registration token failed", task.exception)
            return@OnCompleteListener
        }

        // 現行のトークンを取得
        val token = task.result

        // ログに出力
        Log.d("MainActivity", "Current token: $token")
    })
}

これでアプリ起動時に確実にトークンがログに出力されるようになったかと思います。

※3…公式ではアプリを再インストールした、アプリが新しい端末で復元された、アプリのデータを消去したなどが挙げられています。

メッセージを送ってみる

実はFCMではGUIベースで通知を送信できる機能が用意されているので、ここまで行えばもう自分のアプリに通知を送る事が可能です。

1.FCMコンソールよりSend your first messageのボタンを押して先に進みます。 f:id:hyosh:20210318212923p:plain

2.通知情報を入力します。ひとまず最低限必要なタイトルと本文を入力し「テストメッセージを送信」を押します。 f:id:hyosh:20210318213419p:plain

3.ここでトークンの入力を求められるので、先ほどログに出したトークンを入力しテストボタンを押します。
トークンが間違っていなければアプリの動いている端末に先ほど登録した通知が届いたかと思います。 f:id:hyosh:20210325134156p:plain

4.テスト通知でなく正式な通知として登録するために後続を進めます。
ターゲットではひとまずアプリとする事でアプリ利用者全員に通知を送信できます。スケジュール設定では送信時間や定期的に送るかを指定できます。

確認の後、公開を押します。

5.これで通知が登録できたので、スケジュール設定で指定した時間に通知が届きます。
別の通知を登録したい場合は「新しい通知」より再度登録します。 ちなみにレポートタブからは送った通知の開封率といったデータも見る事ができます。

このように最もシンプルなやり方でも、複数ユーザーに対して時間指定して一斉通知を送る程度であれば簡単に行える事が理解いただけたかと思います。

フォアグラウントで通知を受け取れるようにする

ここまでで通知を受け取ることはできましたが、実は今の実装だけではアプリとしてはまだ不完全です。

というのも今の状態では通知を受け取る事ができるのはアプリがバックグラウンドにある状態のみで、フォアグラウンド(操作している時)の状態では通知を受け取る事ができません。 通知の目的を考えるとフォアグラウンドで受け取る必要性は低いとも言えますが、重要な通知で常にユーザーに認識してほしい局面もあるかと思いますので、少し改造してフォアグラウンドでも通知を受け取れるようにしてみましょう。

1.先ほど作成したMyMessageServiceにonNewToken同様、継承元のFirebaseMessagingServiceで用意されているonMessageReceivedを実装します。

以下ではonMessageReceivedで通知を受け取り送られてきたタイトルとメッセージの通知を生成し、画面に表示します。

override fun onMessageReceived(remoteMessage: RemoteMessage) {
    remoteMessage.notification?.let { it ->
            sendNotification(it)
    }
}

private fun sendNotification(message: RemoteMessage.Notification) {
    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 defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
    val notificationBuilder = NotificationCompat.Builder(this, channelId)
        .setSmallIcon(R.mipmap.ic_launcher)
        .setContentTitle(message.title)
        .setContentText(message.body)
        .setAutoCancel(true)
        .setSound(defaultSoundUri)
        .setContentIntent(pendingIntent)

    val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

    // Android OS 8以降はチャネル指定が必須
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val channel = NotificationChannel(channelId,
            "チャネル説明",
            NotificationManager.IMPORTANCE_DEFAULT)
        notificationManager.createNotificationChannel(channel)
    }

    notificationManager.notify(0 /* ID of notification */, notificationBuilder.build())
}
-- strings.xml
<resources>
    <string name="app_name">NotifyApplication</string>
    //以下一行を追加
    <string name="default_notification_channel_id">fcm_default_channel</string> 
</resources>

最後にAndroidManifest.xmlにサービスを登録します。

<service
    android:name=".MyMessageService"
    android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>

確認する為にコンソールよりテスト送信してみると、フォアグラウンドでも通知を受け取れるようになった事が分かるかと思います。 f:id:hyosh:20210322163921p:plain

(分かりづらいですが左上の●が届いた通知です)

f:id:hyosh:20210325135506p:plain

このようにonMessageReceivedまで実装しておく事でバックグラウンドではFCMよりアプリを通さず直接通知を表示、フォアグラウンドではアプリ経由で通知を表示といったアプリ状態によらず常に表示が可能になります。

HTTPで通知を送ってみる

最後にオプションとしてHTTPリクエストとして通知を送ってみましょう。

1.Firebaseコンソール−プロジェクトの設定−CloudMessagingからサーバーキーを確認します。 f:id:hyosh:20210322180325p:plain

2.FCMの通知送信用として用意されているAPIは「https://fcm.googleapis.com/fcm/send」となるので、これに対し必要なパラメータを付与しPOSTリクエストを投げます。 f:id:hyosh:20210322181852p:plain HeadersのAuthorizationのkeyには先ほど確認したサーバーキー、Content-Typeはapplication/jsonを指定します。

f:id:hyosh:20210322183042p:plain そしてBodyにはtoに対象端末のトークン、notificationにタイトルと本文を指定します。

ツールなどで送信してみるとこのやり方でも端末に通知が届く事が分かるかと思います。

実際のシステムではタイミングが決まっておらず何らかの処理をトリガーに通知を送りたいといったシーンも多いかと思われますので、そのような時に活用できるでしょう。 なお今回は簡易なHTTPを用いましたが公式ガイドで推奨されている後継のプロトコルが存在するため、可能であればそちらを使う事が望ましいです。

まとめ

今回はFirebaseを用いたプッシュ通知実装に関してご紹介させていただきましたがいかがだったでしょうか。

FCMを用いることで思ったよりも簡単に実現できることを理解いただけたのではないかと思います。

iOSとの連携に関してはご紹介できませんでしたが、クライアント側の実装としてはほぼ同じとなりOS間の違いを意識することなく開発する事が可能なので、興味が湧きましたらぜひ試していただけますと幸いです。

最後までお読みいただきありがとうございました。


  • エンジニア中途採用サイト
    ラクスでは、エンジニア・デザイナーの中途採用を積極的に行っております!
    ご興味ありましたら是非ご確認をお願いします。 career-recruit.rakus.co.jp

  • カジュアル面談お申込みフォーム
    どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。
    以下フォームよりお申込みください。
    forms.gle

  • イベント情報
    会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! rakus.connpass.com

Copyright © RAKUS Co., Ltd. All rights reserved.