Firebase

Cloud Functionsでモバイルにリッチプッシュ通知を送信する方法

Cloud Functions、便利ですよね。

こんにちは、株式会社Playground Webシステム開発部の稲垣です!

前回はCloud Functionsに色々な処理を任せてしまおうという話をしました。

今回は、ほとんどどのアプリでも実装することになるであろう「Push通知」について解説します。

FCMトークンの設定や受信時の処理はモバイル側で実装する必要がありますが、「送信」についてはCloud Functionsで実装できます。

今回実装したい関数は以下のような要件と仮定します。

機能要件
  • アプリ管理者がWebの管理画面から配信したいお知らせを登録する
  • お知らせ登録時にユーザーにプッシュ通知で配信される
  • 画像や通知音付きのリッチプッシュにしたい

Cloud Functionsでリッチプッシュを送信する関数を作ろう

実際にPlaygroundで使っているコードを例にしながら解説していきます。

プッシュ通知を送信するトリガーの設定

「アプリ管理者によるニュース登録」をトリガーとしたいので、newsモデル配下にデータが作成された時に、Cloud Functionsが起動するように設定する。

該当のコードは以下の通りです。

functions/src/index.ts

export const sendNotification = functions.database.ref("/news/{newsId}")
    .onCreate(async (snapshot: any, context: any) => {

Cloud Functionsからモバイルデバイスに送信するデータの作成

次に、Push通知で送信するデータを作ります。

FirebaseのFCMメッセージは2種類のデータを格納できます。

  1. notification(通知メッセージ)
  2. data(データメッセージ)

notificationには事前に定義された値を格納できます。

一方、dataは開発者が自由に値を設定できるオブジェクトです。

詳細はFirebaseの公式ドキュメントを参照してください。

弊社ではPush通知=「バッジ+画像+通知音付きのリッチプッシュ」であることが多いので、その場合の具体例を以下に示します。

functions/src/index.ts

const value = snapshot.val();    //新規作成されたニュースの値を格納
const message = {
    notification: {
        title: value.subject,    // 通知のタイトル
        body: value.description,    // 通知の本文
        sound: "default",    // 受信時の通知音
        mutable_content: 'true',    // 画像付きのリッチプッシュに必要
        content_available: 'true'    // アプリがバックグラウンドでも通知を届けるために必要
    },
    data: {
        "image-url": value.image_url    // Cloud Storageに格納している画像ファイルのURL
    }
};

Cloud Functionsからモバイルにリッチプッシュを送信するメソッドの使い方

Cloud FunctionsからモバイルにPush通知を送る時は、Firebase Admin SDKのメッセージを使います。

Push通知を一斉配信するために、sendAll()やsendMulticast()が使えたら便利なのですが、これらのメソッドが受け取れる値はMessage型だけなんですよね。

Message型はnotificationにsoundやmutable_contentといった値を設定できません。

つまり、リッチプッシュを実装できないということです。
(iOS/Androidアプリ側でこねくり回せばできるのかもしれません)

notificationにsoundなどの値を設定できるのは、MessagingPayload型だけ。

そしてMessagingPayload型を渡せるのはSendToXXXXX()メソッドのみです。

ということで仕方なくここではSendToDevice()を使います。

実際にメッセージを送信するための関数は以下のように実装しました。

functions/src/index.ts

const sendMessage = (fcmToken: string, message: any, options: any) => {
    return admin.messaging().sendToDevice(fcmToken, message, options)
        .then(() => {
            return Promise.resolve(null)
        })
        .catch(() => {
            return Promise.reject(null)
        });
    };

この関数はPromiseを返します。
あとでPromise.all()を使って配信を並列処理にするためです。

Cloud Functionsからリッチプッシュ通知を一斉配信する方法

Push通知を一斉配信する箇所の処理の流れは以下の通りです。

  1. userモデルから取得したデータからFCMトークンを取り出す
  2. FCMトークンが登録されているユーザーなら、promisesという配列にsendMessage()を引数と共に追加する
  3. 1〜2を全ユーザー分繰り返したら、最後にPromise.all(promises)で並列実行する。

実際のコードは以下のようになります。

functions/src/index.ts

const users: any = await queryUserData(); // Userモデルからデータを取得
const userModelLength = users.numChildren(); // Userモデルのデータ数を取得

let promises: Array = [],
    counter = 0;

users.forEach((user: any) => {
    const userValue = user.val();
    const fcmToken: string = (userValue.fcmToken) ? userValue.fcmToken : "";
    if (fcmToken !== "") {
        promises.push(sendMessage(fcmToken, message, options))
    }

    counter += 1
    // 全ユーザーの繰り返し処理完了後にPromise.all()で実行する
    if (counter === userModelLength) { return Promise.all(promises) }
})

Cloud Functionsからモバイルにリッチプッシュ通知を送信するために気をつけること

このFunctionを実装する際にハマった点があるので共有します。

Push通知の注意点
  1. FCMトークンが登録されていないユーザーには送信できないので、例外処理や条件分岐が必要
  2. soundやmutable_contentなどを設定したい時はsendメソッドは使えないのでSendToメソッドを使う
  3. Promise.all()で並列処理にしないと、ユーザー数が多い場合、配信時間にタイムラグがでる
  4. ただし並列処理にする場合、Cloud Functionsのメモリ上限を気にしなきゃいけない

以上です。

Push通知が実装できると一気にアプリでできる幅が広がります。

トリガーを工夫すれば、色々なタイミングでPush通知を送ることができますよ。

ABOUT ME
稲垣 貴映
サーバーエンジニア兼Webフロントエンジニア。 新卒で独立系SIerに入社後、金融系システム基盤の構築・運用を3年間経験。 プログラミングを独学していた頃に、CEOの馬谷が開講していたSwiftスクールに通い、2019年9月に入社。 趣味はブラジル音楽の演奏。

POSTED COMMENT

  1. hanawat より:

    ちょうど調べていて試していたのですが、Messageでもこれで行けましたよ!
    https://firebase.google.com/docs/reference/admin/node/admin.messaging.Aps.html#mutablecontent

    • 稲垣 貴映 より:

      コメント有り難うございます!とても参考になります。

      contentAvailableやcontentMutableというプロパティが有効なんですね。
      ドキュメントをしっかり読み込まないとですね(^^;)

COMMENT

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA