[Kotlin]Firebase Cloud Messagingの実装

# はじめに
 - プッシュ通知の実装

# 画面
Firebase ConsoleのCloud Messaging

コンソール上で各項目を設定してプッシュ通知を送信できます。

<img width="960" alt="スクリーンショット 2020-05-05 14.10.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199764/1c80063b-47a4-3f66-0bd4-085890daa1cb.png">

今回は "テスト メッセージを送信"　にて単一端末に対してプッシュ通知を送信します。
<img width="934" alt="スクリーンショット 2020-05-05 14.10.39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199764/b15b7933-ce5e-8c0a-7d3c-729951590eeb.png">


Activityは何もない画面

```activity_cloud_messaging.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.CloudMessagingActivity">

</androidx.constraintlayout.widget.ConstraintLayout>
```

プッシュ通知を受信した際の表示

<img width="375" alt="File.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199764/c13937bb-0c8b-82ae-1be0-83545816d874.png">


# 実装

applicationレベルのgradle

```build.gradle(app)

dependencies {
   〜

    // Cloud Messaging
    implementation 'com.google.firebase:firebase-messaging:20.1.6'
}
```

AndroidManifest

```AndroidManifest.xml

    <application>
        〜

        <!-- new tokenの生成及びメッセージの受信 -->
        <service
            android:name="MyFirebaseMessagingService"
            android:exported="false">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>

        <!-- デフォルトアイコンの指定 -->
        <meta-data
            android:name="com.google.firebase.messaging.default_notification_icon"
            android:resource="@mipmap/ic_launcher_round" />
        <!-- アイコンの色 -->
        <meta-data
            android:name="com.google.firebase.messaging.default_notification_color"
            android:resource="@color/colorAccent" />
        <!-- Android8以降用のデフォルトチャンネル名 -->
        <meta-data
            android:name="com.google.firebase.messaging.default_notification_channel_id"
            android:value="@string/default_notification_channel_id" />
        <!-- インスタンスIDの自動生成を禁止にする場合指定 -->
<!--        <meta-data-->
<!--            android:name="firebase_messaging_auto_init_enabled"-->
<!--            android:value="false" />-->
<!--        <meta-data-->
<!--            android:name="firebase_analytics_collection_enabled"-->
<!--            android:value="false" />-->

    </application>
```

strings.xml

```strings.xml
<resources>
    〜

    <string name="default_notification_channel_id" translatable="false">fcm_default_channel</string>
</resources>
```

MyFirebaseMessagingService
新しいtokenの生成及び、プッシュ通知を受信した場合、こちらで処理して表示する

```MyFirebaseMessagingService.kt
class MyFirebaseMessagingService : FirebaseMessagingService() {

    override fun onNewToken(token: String) {
        Timber.d("Refreshed token: $token")

        sendRegistrationToServer(token)
    }

    private fun sendRegistrationToServer(token: String?) {
        // tokenをserverへ送る
        Timber.d("sendRegistrationTokenToServer($token)")
    }

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

        // TODO(developer): Handle FCM messages here.
        // Not getting messages here? See why this may be: https://goo.gl/39bRNJ
        Timber.d("From: ${remoteMessage.from}")

        // Check if message contains a notification payload.
        remoteMessage.notification?.body?.let { body ->
            Timber.d("Message Notification Body: $body")
            sendNotification(body)
        }
    }

    // notificationを生成して表示
    private fun sendNotification(messageBody: String) {
        val intent = Intent(this, MainActivity::class.java)
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
        val pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
            PendingIntent.FLAG_ONE_SHOT)

        val channelId = getString(R.string.default_notification_channel_id)
        val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
        val notificationBuilder = NotificationCompat.Builder(this, channelId)
            .setSmallIcon(R.mipmap.ic_launcher)
            .setContentTitle("FCM Message")
            .setContentText(messageBody)
            .setAutoCancel(true)
            .setSound(defaultSoundUri)
            .setContentIntent(pendingIntent)

        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        // Since android Oreo notification channel is needed.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(channelId,
                "Channel human readable title",
                NotificationManager.IMPORTANCE_DEFAULT)
            notificationManager.createNotificationChannel(channel)
        }

        notificationManager.notify(0 /* ID of notification */, notificationBuilder.build())
    }

}
```

Activity

```CloudMessagingActivity.kt
class CloudMessagingActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_cloud_messaging)

        // インスタンスIDの自動生成を有効化する場合、true
        // AndroidManifestにて自動生成を禁止にしていない場合、不要
//        FirebaseMessaging.getInstance().isAutoInitEnabled = true

        // Current Notificationトークンの取得
        FirebaseInstanceId.getInstance().instanceId
            .addOnCompleteListener(OnCompleteListener { task ->
                if (!task.isSuccessful) {
                    // token取得失敗
                    Timber.d("getInstanceId failed ${task.exception}")
                    return@OnCompleteListener
                }

                // new Instance ID token
                // ここで取得したtokenをテストする際のインスタンスIDとして設定する
                val token = task.result?.token

                val msg = "InstanceID Token: $token"
                Timber.d(msg)
                Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
            })
    }
}
```

# おわりに
 - 実装する箇所が他のFirabaseの機能に比べて多く、ミスが多くなるかもしれませんが、
それぞれの意味を理解するとミスが減るかと
 - 通常はサーバにtokenを送ってサーバからプッシュ通知を受信するのがベターです
 - サーバ側を実装しなかったとしても今回のようにconsoleから送ることも可能です
 - consoleからでもプッシュ通知を送信するターゲットや送るタイミングを指定すること可能です
 
ソースコードは[こちら](https://github.com/yoshihitoijichi/FireabseSample)