FirebaseRemoteConfigによって分割されたユーザセグメントごとにプッシュ通知を送信する

# はじめに

Firebase最近流行っていますね。Firebaseにはたくさんのサービスがありますが、
ここでは、その中でも「Firebase Notification」というサービスを使って、通知対象ユーザを分割して、プッシュ通知をインターバル送信する方法について紹介していこうと思います。

# 背景

ちょっと長くなるので、すぐ方法を知りたいという方は飛ばしていただいて大丈夫です。

Firebaseには通知を比較的簡単に送信することができるFirebaseNotificationと呼ばれるサービスがあり、実際に私も使用しております。管理ツール的なものはFirebase上のコンソールで用意されているので、プッシュ通知基盤のような大掛かりなものを作らなくてもプッシュ通知施策が打てるというとても魅力的なサービスです。

ただ、プッシュ通知施策をやる上で、忘れてはならないことがあります。それはサーバの負荷の問題です。
一般的なアプリの仕組みとして起動時にAPIリクエストが走るようなアプリが多いと思います。
また、プッシュ通知を送信すると、通知の内容やサービスの種類にもよりますが、だいたいAndroidで13%,iOSで4.3%ほどの開封率で通知を開いてアプリが起動されるそうです。アクティブユーザ数が100万人を超えるAndroidアプリとかであれば、約10万人が一斉にアプリを開く事になりますね。
それだけ全員が一斉にアプリを開く。つまり、APIリクエストが一斉に増えることでサーバへの負荷が当然増加します。
プッシュ通知は、アクティブユーザ数を上げるための効果的な施策でありながら、意外とサーバサイドへの負荷という面を考慮しなければならないところがあります。

そこで、その負荷を分散する方法として、サーバの台数を増やしたり、通知開封時には通信を走らせないなどの対策が考えられますが、その中でも送信対象を分割してインターバル送信をする事で、負荷分散するという方法も一般的だと思います。

ということで、今回はFirebaseを使った一番手っ取り早くプッシュ通知をインターバル送信をする方法を紹介していこうと思います。

# Firebaseとは

Firebaseとは、「すばやく高品質のモバイルアプリを開発することができるプラットフォーム」であり、Function、データストア、プッシュ通知、ユーザ分析、クラッシュレポートなど多くの機能を使用することができます。

https://firebase.google.com/?hl=ja

## 今回使用するサービス

今回Firebaseでインターバル送信をする上で、使用するサービスは以下となります。

### FirebaseNotification

モバイルアプリのデベロッパーがターゲットユーザー向けの通知を使用できるようにする無料のサービス。

https://firebase.google.com/docs/notifications/?hl=ja

### RemoteConfig

アプリに依存せず、クラウドを通じて環境変数的なものを提供してくれるサービス。受信したConfig値によって、アプリの動作と外観を変更することが可能

https://firebase.google.com/docs/remote-config/?hl=ja

### UserProperty

Firebaseのアナリティクスに当たるサービスの一つ。ユーザを属性ごとにセグメント分けすることができる。

https://firebase.google.com/docs/analytics/android/properties?hl=ja

# 全体像

こちらがサービスの全体像となります。

![Untitled.png](https://qiita-image-store.s3.amazonaws.com/0/147934/e35c4ad4-fd41-048a-a8ac-ff1ce2760c5d.png)

説明しますと、、

1. ユーザ分割用のRemoteConfigを作成(均等の確率で条件を分ける)
2. アプリ側で1のConfigを取得
3. 取得した値をUserPropertyに登録
4. NotificationComposer(FirebaseConsole)で通知を作成。この時通知をセグメントごとに作成し、それぞれの時間をずらして、配信予約（スケジューリング）
5. 通知配信後、アプリで通知を受け取る

という流れになります。
なかなか強引な方法だと感じる人が多いとは思いますが、FirebaseNotificationだけで完結させようとした場合これが、一番楽な方法なのかなと思います。
また、そもそもなんでUserPropertyに登録するというまどろっこしい方法をとるかというとNotificationComposer上では、RemoteConfigによる配信対象の設定ができないためです。

では、実装手順を説明していきます。

# 実装手順

私は、Androidエンジニアなので、Androidの実装手順を解説していきます。
ただこの仕組み自体は、iOSでも応用が可能ですので、是非試していただければと思います。

## Step0: Firebaseの準備

まず、FirebaseConsoleでアプリを登録します。登録方法は割愛しますので、以下を参照ください。

https://firebase.google.com/docs/guides/?hl=ja

## Step1: アプリ内の準備

Firebaseでのアプリの登録が済んだら、GradleのdependenciesにFirebaseのSDKを追加していきます。

- プロジェクトルートのbuild.gradleに以下を追加。

```build.gradle
buildscript {
    // ...
    dependencies {
        // ...
        classpath 'com.google.gms:google-services:4.1.0' // google-services plugin
    }
}

allprojects {
    // ...
    repositories {
        // ...
        google() // Google's Maven repository
    }
}
```
- app/build.gradleに以下を追加

```app/build.gradle

apply plugin: 'com.android.application'

android {
  // ...
}

dependencies {
  // ...
  implementation 'com.google.firebase:firebase-core:16.0.4'
  implementation 'com.google.firebase:firebase-config:16.0.4'
  implementation 'com.google.firebase:firebase-messaging:16.0.4'
}

// 末尾でないといけないそうです
apply plugin: 'com.google.gms.google-services'

```

## Step2: RemoteConfigの設定

### FirebaseConsoleでRemoteConfig値を設定

① Config値を作るのが初めてであれば下画像の「最初のパラメータを追加」をクリック。すでに別のConfigを作成したことがあれば、「パラメータを追加」をクリック。

<img width="1173" alt="RemoteConfigのConfig作成" src="https://qiita-image-store.s3.amazonaws.com/0/147934/d61f64e1-b361-6fa6-03f8-b801868fd2ba.png">

② パラメータキーと各config値、条件を定義します。今回は、ユーザを三分割して、送る想定で、以下のようにconfig値を設定します。

```
パラメータキー: user_group
デフォルト値: 0
user1: 
  名前: user1
  条件: ユーザ(ランダム%) <= 33%
  値: 1
user2: 
  名前: user2
  条件: ユーザ(ランダム%) > 33% かつ <= 66%
  値: 2
user1: 
  名前: user3
  条件: ユーザ(ランダム%) > 66%
  値: 3
```

設定すると以下のような表示になります。各条件は「右上の条件の値を追加」から定義することができます。
<img width="1022" alt="Config値の定義" src="https://qiita-image-store.s3.amazonaws.com/0/147934/8659121e-b505-8aa1-2c4a-03489c3cd7ef.png">

これでコンソール側の設定は完了です。

### アプリ側からRemoteConfigを取得

ではコンソールで設定したConfig値を取得してみます。MainActivityまたは、Applicationクラスを継承しているクラスのonCreate内で以下の処理を記述します。(Javaですみません。。)
すると、コンソール側で設定した割合によって、ランダムにconfig値が割り当てられます。初期起動時は、まだFirebaseサーバ側にデバイスが識別されていない段階なので、ローカルであらかじめ設定したデフォルト値が取得されます。(setDefaultで設定した値)

詳しくは、公式ドキュメントにも記載されているので、気になる方はそちらを参照ください。

```MainActivity.java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initializeFirebaseRemoteConfig();
    }

    private void initializeFirebaseRemoteConfig() {

        FirebaseRemoteConfigSettings config = new FirebaseRemoteConfigSettings.Builder().setDeveloperModeEnabled(BuildConfig.DEBUG).build();
        FirebaseRemoteConfig remoteConfig = FirebaseRemoteConfig.getInstance();
        remoteConfig.setConfigSettings(config);
        remoteConfig.setDefaults(R.xml.remote_config_default_value);

        Task<Void> task;
        if (config.isDeveloperModeEnabled()) {
            // 開発モード：キャッシュの有効時間を 0 で設定
            task = remoteConfig.fetch(0);
        } else {
            // 本番モード：キャッシュの有効期間はデフォルト 12 時間
            task = remoteConfig.fetch();
        }
        task.addOnCompleteListener(fetchedTask -> {
            if (fetchedTask.isSuccessful()) {
                remoteConfig.activateFetched();
                //コンソールで設定したパラメータでConfigを取得。
                String userGroup = remoteConfig.getString("user_group");
            }
        });
    }

}
```

## Step3: UserPropertyにConfig値を登録

Step2でConfigの取得ができましたら、次は取得した値をUserPropertyに登録させます。
UserPropertyの設定には、`FirebaseAnalytics`のインスタンスが必要になるので、そのシングルトンオブジェクトを取得して、`setUserProperty()`メソッドで登録します。

```MainActivity.java
public class MainActivity extends AppCompatActivity {

    private FirebaseAnalytics mFirebaseAnalytics; //追加

    @Override
    protected void onCreate(Bundle savedInstanceState) {
      //省略
      mFirebaseAnalytics = FirebaseAnalytics.getInstance(this); //追加
      initializeFirebaseRemoteConfig();
    }

    private void initializeFirebaseRemoteConfig() {
　　　　　　　　　　　　　　　　//省略
        task.addOnCompleteListener(fetchedTask -> {
            if (fetchedTask.isSuccessful()) {
                remoteConfig.activateFetched();
                String userGroup = remoteConfig.getString("user_group");
                //↓を追加
                mFirebaseAnalytics.setUserProperty("user_group", userGroup);
            }
        });
    }

}
```

## Step4: アプリにFirebaseNotificationの導入

さて、アプリ側で通知を受け取れるようにします。通知を受け取れるようにするには、FirebaseCloudMessagingと呼ばれるサービスを使用します。基本的にデフォルトの実装で今回のやりたいことは実現できるので、導入方法は、ドキュメントを参照いただければと思います。

- FirebaseCloudMessaging(概要)
https://firebase.google.com/docs/cloud-messaging/?hl=ja

- 導入方法(Android)
https://firebase.google.com/docs/cloud-messaging/android/client?hl=ja

- 導入方法(iOS)
https://firebase.google.com/docs/cloud-messaging/ios/client?hl=ja

## Step5: NotificationComposerで通知を送信

では、ついに通知を送信します。

① メニューから「CloudMessaging」をクリックし、「新しい通知」をクリック。

② 通知メッセージ文を入力

③ 「ターゲット」で、以下のように入力

アプリ: 対象となるアプリを選択
右の「かつ」をクリックし、
「ユーザプロパティ」 > 「user_group」を選択。
「完全一致」で対象となるuser_groupのconfig値を入力。
ここの条件は自由なので、user_groupを例えば10パターンくらいにしておいて、「1,2,3」「4,5,6」「7,8,9,10」といったような形で分けることも可能です。


<img width="1035" alt="通知の送信" src="https://qiita-image-store.s3.amazonaws.com/0/147934/2d809b91-0ef2-6cce-e44b-daa93f986175.png">


④ 「スケジュール設定」で送信予定となる時間を設定します。

この作業をuser_groupの数の分それぞれスケジュールを設定します。

### 通知作成後

通知をuser_group3つ分で作ると以下のようになります。各user_groupごとに配信予定となる時間を設定しています。ここの配信間隔はインフラやサーバサイドの方と話し合って、どれくらいの間隔で配信するのが妥当なのかを話し合うと良いかなと思います。

<img width="1107" alt="通知作成後" src="https://qiita-image-store.s3.amazonaws.com/0/147934/10a72d85-0e5a-4c88-8b53-4d8248984a48.png">

最近Firebaseのレイアウトが崩れていますが、そこは気にせず、、

これであとは通知が配信されるのを待つだけです。

## 注意点

- UserPropertyへの登録は即時反映ではありません。したがって、アプリをインストールばかりのユーザにはPropertyを指定して、通知を送信しても通知が届かない可能性があります。
このラグが許容できないのであれば、user_groupをカスタムデータとして通知の中に含めておいて、アプリ側で表示するかしないかをハンドリングする必要がありますが、セグメントが増えればその分比例して通信量が多くなるので、あまりいい方法とは言えないと思います。

- 当然、ユーザが増えればRemoteConfig値(user_group)を増やす必要があります。RemoteConfigの値は追加すれば再計算してくれる（はず）なので、もともと設定していたgroupにユーザが偏ってしまうということはないとは思いますが、運用的には、あまりよろしくはないですよね、、

- 結局NotificationComposer上のスケージューリングの設定等は、手作業が発生しますので、ヒューマンエラーが起きる可能性は大いにあります。そこで、実際に使用する上では、運用フロー的なものを明確にして、企画・開発のダブルチェックを行うなどの対策を講じる必要もあると思います

# まとめ

簡単に使用することができるFirebaseNotificationですが、実際に実運用しようとし始めると辛い部分が多い印象です。
RemoteConfigの値をNotificationComposerでそのまま指定して使えればなあ、、
今回の方法も割と強引なやり方だと思うので、リソースやスケジュール的に余裕があるのであれば、独自の配信基盤などを作って、より正確な配信ができるようにするのが理想だとは思います。

サンプルのソースコードは以下に載せているので、よかったら参考にしてください。
https://github.com/youmitsu/FirebaseAnalyticsSample

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