複数端末を所持している人を考慮してプッシュ通知を送る

Gmailとかハングアウトとかって、複数端末で通知を受けても、１つの端末で消せばちゃんと全部の端末で通知消えるんですよね。

これがやりたい。

あれ、どうやるの？　という話です。


## ざっくり

プッシュを受け取った端末が「受け取ったよ！」って空プッシュを送って send-to-sync する、みたいなことをやらないといけないようです。
![Slice.png](https://qiita-image-store.s3.amazonaws.com/0/74571/c5f82777-0983-3b74-2ed1-b4add90b1868.png)


以下のリファレンスを見ると、Device Group Messagingというのをつかうだけで、GCMがいい感じにやってくれるようにも見えますが、実際に動かしてみると、プッシュ消去は同期されませんでした・・・・・・。

http://www.androiddocs.com/google/gcm/notifications.html
https://firebase.google.com/docs/cloud-messaging/android/device-group


## Device Group Messaging

![Slice.png](https://qiita-image-store.s3.amazonaws.com/0/74571/568bf90f-b0e1-3772-95e3-8e23530375b1.png)

FCMには、メッセージ送信の `to` パラメータにグループを指定することで、そのグループに属する端末たちの通知が同期される仕組みがあります。
グループがあれば、個々のデバイストークンを指定せずにメッセージが送れるので、ユーザ単位でプッシュを送りたいケースでは、１デバイスであっても使う価値はありそうです。


## グループ？

https://android.googleapis.com/gcm/notification というエンドポイントにPOSTリクエストを出すことで、グループのメンバー管理ができるようです。


### 初めてグループを作るとき

```
{
   "operation": "create",
   "notification_key_name": "HogeAppUser0003",
   "registration_ids": ["xxxxx", "yyyyy"]
}
```

みたいなのをPOSTすると、グループのID文字列（notification_key）が返ってくるので、
それをどこかに覚えておいて送信時に使います。

registration_id は、Androidアプリ側で `FirebaseInstanceId.getInstance().getToken();` で取得できるやつを指定。(これ、リファレンスには曖昧な書き方されてて、ちょっとハマるとこｗ)


### グループにメンバーを追加・削除するとき

```
{
   "operation": "add",
   "notification_key": "作成時にもらったグループのID文字列",
   "registration_ids": ["zzzzz"]
}
```

とか

```
{
   "operation": "remove",
   "notification_key": "作成時にもらったグループのID文字列",
   "registration_ids": ["xxxxx"]
}
```

とかをPOSTすると、グループのID文字列（notification_key）が返ってきて成功！　となります。


### 非常にくせのある仕様・・・

https://android.googleapis.com/gcm/notification のAPIは、
グループが０人か１人以上かによって挙動が変わります。これがすごくめんどくさい。

おそらく

* ０人の時→createのみ可能。新規notification_keyが発行される。
* １人の時→add/removeのみ可能。notification_keyが引き継がれる

のような仕様なんだと思うんですが、

* `create` したあと `create`　→４００エラー
* `create` したあと同じ人を `add` →200 OK (notification_keyが返る)
* `add` したあと同じ人を `add`　→200 OK (notification_keyが返る)

あたりはわかるんですが、

* `create`もしくは`add`した人を `remove`　→200 OK 　・・・ **notification_keyが返るが、グループの残りが０人だったら、もうこのnotification_keyは使えない**
* `remove` した人を `remove` → **グループメンバーがのこり０人であれば 400エラー** 、１人以上いたら 200 OK
* `remove` した人を `add` → **グループメンバーがのこり０人であれば 400エラー** 、１人以上いたら 200 OK
* `remove` した人を `create` →グループメンバーが０人であれば 200 OK、 **１人以上いたら 400エラー**

あたりは、かなりくせっぽいなー　（≒ こんな管理をやりたくないなー） と・・・ｗ


ちなみに、Androidアプリから登録する際には、notification_key の指定無しで add/removeができるので、このようなめんどくさい考慮はいりません。（が、別のめんどくさい考慮が必要です・・・後述）
　
　
## 誰がメンバー管理をするべきか？

基本的に、「サーバーサイドで端末グループ管理なんて自前でやりたくないよー」っていう場合のことを書きます。

### パターン１ グループ登録までAndroid側でやる

Android側で

* ユーザIDから、一意で推測されにくい文字列を生成する　・・・これをグループ名としてつかう
* アプリにログイン完了後、FCMトークンを取得し、グループを作成 or メンバー追加する
* グループIDをサーバに送る

みたいなことをやる流れになります。

サーバーサイドでは

```
User has_one fcm_group_key
```

みたいな関連を持っておくだけでプッシュが送れるので、シンプルな構成になります。

ただ、AndroidのAPIは、 `id_token` が必要で、こいつは **Googleログインを組み込んであげないと取得できない** シロモノでして・・・
まぁ頑張って実装しましょう・・・。

　

とはいかないこともあるので・・・

### パターン2 サーバー側でグループ登録処理だけはやる

Android側

* アプリにログイン完了後、FCMトークンを取得後、それをサーバーに送る

サーバー側

* もらったFCMトークンをもとに、グループを作成 or メンバー追加する
* グループIDを登録する

という流れになります。

この場合は

```
User
 has_many fcm_tokens
 has_one fcm_group_key
```

のように２つの関連をもつことになるでしょう。

誰がどのグループに属しているかは知らなくてもよいですが、先に述べたような「くせのある仕様」を攻略する必要があります。

とはいえ、端末個別にもプッシュはおくりたいし、端末グループ単位（ユーザごと）にもプッシュはおくりたい、　という場合は必然的にこちらのパターンを採用することになるでしょう・・・。

　　
　　


**で、どっちがいいの？** といわれると、

* サーバー側でFCMトークンを持つということ自体が何かと不便
* そもそも端末単位で送りたいプッシュなんて、そんなにないでしょ？

という独断と偏見で、私はパターン１をおすすめします。　が、システムによって要件はまちまちなので、そこは適宜考えて下さい。


## まとめ

まず、プッシュ通知をおくる、となったときには

* 重要じゃない（≒ アプリの機能と関係のない）通知を送ろうとしてないか？
* プッシュ通知頻度は適正か？

だけでなく、

* 複数端末にプッシュした際に、プッシュの消去が同期されるべきか、否か？

という観点を持ちましょう。

プッシュの消去を同期したかったら  [Device Group Messaging](https://firebase.google.com/docs/cloud-messaging/android/device-group) を使いましょう。