Cloud FunctionsでFirestoreの更新を特定のデバイスにpush通知する

# Cloud Functions × Firestore × Firebase Cloud Messaging

Cloud Functionsはサーバレスコンピューティングサービスです。様々なイベントをトリガーにしてアクションを実行することができ非常にコストパフォーマンスに優れています。

今回はこのCloud Functionsを用いてFirestoreにデータが追加されたら特定のデバイスにpush通知が届くようにします。

# Structure

通知を送信したいDBを設計します。今回は簡略化のために`notifications`というコレクションにしています。
ここであらかじめテーブルにデバイスのidが紐付いているとやりやすいと思います。


```terminal
notifications
│
├── notification
│   ├── text
│   └── users
.        ├── bJi8iJLw0ufF1HCO17v3b7UTVS53
.        .
.        .
```

# Architecture



<img width="925" alt="IMG_B2ADF5BEBE4E-1.jpeg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243995/2003a156-0b63-a54f-2b0f-fa2a8507af83.jpeg">



# Steps

以下のことが既に完了している前提で実装していきます。多分この記事にたどり着くような人はコーディングで行き詰まった人だと思います。Firestoreのサンプルが[ここしかなかった。。。](https://codelabs.developers.google.com/codelabs/firebase-cloud-functions/#9)

- Firebaseプロジェクトは作成済み
- 必要なパッケージはインストール済み
- クライアント側のリモート通知設定済み

## 1. Cloud Functionsをスタートする

まずプロジェクトを作成します

```terminal
$ Firebase init
```

<br>

functionsディレクトリ内にある`index.js`にコードを記述していきます。

## 2. コードを記述



```js:index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();

exports.sendNotifications = functions.firestore.document('notifications/{notificationId}').onCreate(
    async (snapshot) => {
        // Notification details.
        const newValue = snapshot.data();

        const payload = {
            notification: {
                title:"新規ライブ",
                body: newValue.text + "の新しいライブが追加されました!",
                content_available: 'true',
                sound: "default",
                click_action: `https://${process.env.GCLOUD_PROJECT}.firebaseapp.com`,
            }
        };

        const userIDs = newValue.users

        // Get the list of device tokens.
        userIDs.forEach(function (value) {
            admin.firestore().collection('fcmTokens').doc(value).get()
                .then(function (querySnapshot) {

                    let fcmToken = querySnapshot.data().fcmToken
                    admin.messaging().sendToDevice(fcmToken, payload);
                    return;
                }).catch(error => { return });
        });
    });

```

### なんやこれポイント

サンプルに `const allTokens = await admin.firestore().collection('fcmTokens').get();` ってあったのでてっきりFirestoreのfcmToken勝手にここに格納されてるのかと思いきや自分でDBつくるっぽかった。なので、userIdと紐付けて識別できるようにしました。

```
fcmTokens
│
├── fcmToken(id: uid)
│   └── fcmToken
.       
.    
```

まず`functions.firestore.document()`の引数にコレクションを指定します。今回はテーブルに新たなドキュメントが追加されたときに発火するようにしたいので`onCreate()`を呼び出しています。
その他にも`onUpdate()`、`onDelete()`などさまざまなトリガーがあります。詳しくは[こちら](https://firebase.google.com/docs/firestore/extend-with-functions)。

そして、`payload`に通知の設定をします。

`fcmTokens`というコレクションからfcmtokenをゲットすることができます。RealtimeDBより便利な気がする。

最後に`sendToDevice()`で取得したユーザのトークンにまとめてpush通知を送信しています。

## 3. Deploy

```
$ firebase deploy
```

なんか死んでたらログを確認してちょこちょこ直していきましょう。


## 4. Test

ためしにPythonからFirestoreに新しいドキュメントを追加してみます

```Python
import firebase_admin
from firebase_admin import credentials
from firebase_admin import firestore


cred = credentials.Certificate()
firebase_admin.initialize_app()
db = firestore.client()

notification_data = {
    "text": "MY FIRST STORY",
    "users": ["gHrdAxQhFYdTmoFjgi8RX8PljXv1"]
}

db.collection(u'notifications').document().set(notification_data)
```

<br>

任意のデバイスにpush通知が来ればオッケー。たっくーTV見てたのは内緒。


<img width="414" alt="IMG_9239.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243995/c7c59855-ae28-d401-b79a-53e48e896088.jpeg">

コードは[こちら](https://github.com/masatsch/FirebaseCloudFunctionsPushNotification)