AWS SNSからAndroidにプッシュ通知するためにやったこと、ハマったこと

今関わっているプロジェクトのバックエンドはAWSです
そんなAWSからユーザが操作するAndroidにプッシュ通知がしたいと思いました
しかしFirebase(FCM)が便利すぎて逆にハマってしまったのでメモしておきます

プッシュ通知なんてどうせみんなオフするからいらん！という漢気も私は好きです

参考にした素晴らしい記事様はこちら
[Android から Amazon SNS を使ってみる - Qiita](https://qiita.com/kusokamayarou/items/27e023ad06cade20c731)
[Android端末で、FCM経由でAWSSNSを受け取るまで - Qiita](https://qiita.com/k_keisuke/items/24afa62955808721033e)

# 目指すもの
![スクリーンショット 2020-12-18 9.40.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/f76788b4-9416-224a-5781-a51e43beee82.png)
まずは最小限、プッシュ通知をすることだけを目標にします
細かい設定や他の人の方が詳しい！

# 用意するもの
* 適当なAndroidプロジェクト
* Firebaseアカウント　※無料プランでOK
* AWSアカウント

# FirebaseからAndroidに通知するまで
## 適当にAndroidプロジェクトを作成する
![スクリーンショット 2020-12-18 9.00.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/f557f7dd-59bb-a22d-ec86-f40e173f5ed1.png)
パッケージ名は後ほど必要です

## Firebaseにアカウントを作成、適当なプロジェクトを作る
![スクリーンショット 2020-12-18 8.53.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/3adeace4-35e3-2e33-1101-738b9a422285.png)
「プロジェクトを追加」から適当な名前でプロジェクトを作成します
ちなみにFCM（Firebase Cloud Messaging)はだけなら無料プランでもよさそうです。素敵。

## Firebase Cloud MessagingにAndroidアプリを登録する
公式がとても丁寧に案内してくれるのでそれに従います

### Androidパッケージを登録する
![スクリーンショット 2020-12-18 9.09.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/6e6ef59f-73b2-8b08-4c4a-7a345b574f46.png)
Androidパッケージ名に、先ほど作成したAndroidプロジェクトのパッケージ名を入力します
署名はよりセキュリティを高めるなら入れた方がいいんでしょうね。今回は省略！

### appディレクトリにJSONファイルを追加
![スクリーンショット 2020-12-18 9.14.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/e0ffb0d6-a907-a3f3-3120-9fce59027878.png)
firebaseからダウンロードしたgoogle-services.jsonをapp以下に追加します

### Androidプロジェクトに依存関係を追加する
```kotlin:/build.gradle
buildscript {
    ext.kotlin_version = "1.4.21"
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.1.1"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "com.google.gms:google-services:4.3.4"  // <<<<< added
        ~
```

```kotlin:/app/build.gradle
plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'com.google.gms.google-services' // <<< added
}
// ~略~
dependencies {
    // ~略~
    implementation platform('com.google.firebase:firebase-bom:26.1.1')  // <<< added
    implementation 'com.google.firebase:firebase-analytics-ktx'         // <<< added
    implementation 'com.google.firebase:firebase-messaging-ktx'         // <<< added
}
```

## FCMにAndroidデバイスを登録する
### 通知用のデバイストークンを取得する
プッシュ通知するためには、Androidデバイスを一意に特定するためのトークンが必要になります
トークンの発行はAndroidプロジェクトに組み込まれたFirebaseがやってくれます
例えば下記のようにtokenをログ出力するようにしてみます

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // ↓added
        FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener {
            if (!it.isSuccessful) {
                Log.e("Firebase", "Fetching FCM registration token failed", it.exception)
                return@OnCompleteListener
            }

            val token = it.result
            Log.d("Firebase Token", token.toString())
        })

    }
}
```

この状態でアプリを動かすと、ログにトークンが吐かれますので、これをメモしておきます

```
D/Firebase Token: c3ilo ~~~
```

## FCMからプッシュ通知を送信してみる
![スクリーンショット 2020-12-18 9.25.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/3a72e15a-cb66-fa10-7523-b640c18f9491.png)
FCMのダッシュボードから「通知の作成」へ行き、早速メッセージを送ってみます

![スクリーンショット 2020-12-18 9.38.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/032e5188-e766-bf0d-c3ac-3ba2cf880e5c.png)
FCM登録トークンに、先ほどAndroidから生成されたトークンを登録して、テストメッセージを送信します

![スクリーンショット 2020-12-18 9.40.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/1b14fac8-e2ed-8589-7c13-f961bc2afddc.png)
すると無事届きました！簡単ですねー
さあFirebase > Android間の連携ができたので、次はAWS SNSとの連携だ

### 注意：アプリがバックグラウンドでないと通知が届かない
デフォルトの状態だと、アプリがフォアグラウンド中（前面表示）には届かないようです
「君もうアプリ開いてるから通知せんでええやろ？」ってことでしょうか

# AWS SNSからAndroidに通知するまで
## AWS SNSとFirebaseを紐付ける
[AWS公式](https://aws.amazon.com/jp/premiumsupport/knowledge-center/create-android-push-messaging-sns/)が非常に親切に手順を説明してくれていますので、基本はこの通りに

### Firebaseのサーバーキーを取得する
![スクリーンショット 2020-12-18 10.54.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/c5a03650-0a6a-b5d7-5f1b-1b5e9772d9d7.png)
プロジェクトの設定→「Cloud Messaging」からサーバーキーを取得します

### FirebaseサーバーキーをAWS SNSに登録する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/2c9f9085-4447-4e57-c315-2e77b9ff87e5.png)

* AWSコンソールからSimple Notification Serviceを開く
* 左バーの「プッシュ通知」からモバイルプッシュ通知画面を開き、「プラットフォームアプリケーションの作成」
* プッシュ通知プラットフォームは「FCM」
* APIキーに先ほど取得したFirebaseサーバーキーを入力する
* そのほかは空欄でOK

### アプリケーションエンドポイントを作成する
![image (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/444e95a4-020d-2d70-3f2e-7b53ed956457.png)

* デバイストークンにAndroidから取得したトークンを入力する
* そのほかは空欄

### いざSNSメッセージ送信！しかし全く反応なし
![image (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/c765b739-6db7-e1e8-fa16-14928644ff1c.png)

なにせFirebaseからAndroidへのプッシュ通知は成功しているので、AWS SNSとFirebase間の紐付けがうまくいってない？
と思ってサーバーキー周りをめちゃくちゃ確認しましたが、原因は別のところにありました
それでは、トラブルシュート編に続きます

# AWS SNSからAndroidに通知するために追加でやるべきこと
## 送信するメッセージにはフォーマットがある
AWS SNSをちょっと知っている人には当たり前だと思いますが...
Firebaseで受け取れるようなメッセージを送るにはフォーマットがあります
しかもAWS側がテンプレートを用意してくれています（カスタムペイロード）

![image (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/178e4901-9bcf-fe7f-b36b-5193382b43b9.png)

しかし、これでもAndroidにはプッシュ通知されませんでした

## Android側でレシーバを実装する必要がある
[Firebase公式](https://firebase.google.com/docs/cloud-messaging/android/receive?hl=ja)にはしっかりとこんな説明があります
> フォアグラウンド アプリで通知メッセージまたはデータ メッセージを受信する場合は、onMessageReceived コールバックを処理するコードを記述する必要があります。

FirebaseからAndroidへの通知は成功していたので、動くと思っていましたが甘かったようです

### Androidマニフェストに通知受信用のサービスを追加
マニフェストファイルの<application>タグ以下にサービスを追加します

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

```kotlin:MyFirebaseMessagingService.kt
class MyFirebaseMessagingService : FirebaseMessagingService() {
    override fun onMessageReceived(message: RemoteMessage) {
        super.onMessageReceived(message)

        Log.d("onMessageReceived", "From: ${message.from}")
        if (message.data.isNotEmpty()) {
            Log.d("onMessageReceived", "payload: ${message.data}")
        }
    }
}
```

しかしこれでもプッシュ通知が飛んでこないなーと、何気なくログを見てたら
![log_image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/1df11a4d-2d15-cdf3-cf47-1e1a0b15ae68.png)
来てたァ！！

## カスタム通知を作成する
公式の[FCMメッセージについて](https://firebase.google.com/docs/cloud-messaging/concept-options?hl=ja)を参照する限り、AWS SNSからのメッセージはデータメッセージとして扱われていて、
その場合はクライアント側で処理をする必要があるとのこと
（SNSから送信するメッセージにdataって付けてますしね）

```kotlin:MyFirebaseMessagingService.kt
class MyFirebaseMessagingService : FirebaseMessagingService() {
    val CHANNEL_ID = "MyNotification"

    override fun onMessageReceived(message: RemoteMessage) {
        super.onMessageReceived(message)

        Log.d("onMessageReceived", "From: ${message.from}")
        if (message.data.isNotEmpty()) {
            Log.d("onMessageReceived", "payload: ${message.data}")
        }

        createNotificationChannel()
        val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("通知がきたで")
            .setContentText("内容は「${message.data}」やで")
            .setSmallIcon(R.drawable.ic_launcher_background)
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
        with(NotificationManagerCompat.from(this)) {
            notify(UUID.randomUUID().hashCode(), notificationBuilder.build())
        }
    }

    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val name = "MyNotificationChannelName"
            val descriptionText = "MyNotificationChannelDescription"
            val importance = NotificationManager.IMPORTANCE_DEFAULT
            val channel = NotificationChannel(CHANNEL_ID, name, importance).apply {
                description = descriptionText
            }
            // Register the channel with the system
            val notificationManager: NotificationManager =
                getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            notificationManager.createNotificationChannel(channel)
        }
    }
}
```

正直ここの実装はかなりみようみまねで適当なので、ご使用の際は十分に注意してください
ちなみにこのとき、アイコンなど一部の設定を省略するとランタイムでクラッシュしたりします

## 無事プッシュ通知ができました！ :tada: :tada: 
![スクリーンショット 2020-12-18 12.01.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/f3e24b9c-0061-574f-d5ce-f73ec8b6151f.png)

ということで無事通知が動きました！
まだ通知周りは詰められてないところもありますが、なんとかなりましたね