AndroidのPush通知の導入方法

![image00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/801359/9e2540eb-3838-9e36-dde2-92f219ee40c8.png)
Firebase Cloud Messaging(FCM)を使って AndroidでPush通知を実装します。

# Firebase Cloud Messagingとは
Firebase Cloud Messaging（FCM）は、メッセージを無料で確実に配信するためのクロスプラットフォーム メッセージング ソリューションです。以前は Google Cloud Messaging API が使われていたようですが、現在はFCMに移行しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/801359/d9f66810-953a-0d8e-273f-5fadef9a9925.png)

# Push通知実装の流れ
1. Firebaseプロジェクトの作成 - Firebaseコンソール
1. Androidアプリでサービスを実装 - Android Studio
1. テスト通知 - Firebaseコンソール
1. アプリがフォアグラウンド時の通知表示実装 - Android Studio

それでは実装していきましょう。

# Firebaseプロジェクトの作成
#### 1 .Firebaseのコンソールから新規にプロジェクトを作成
https://console.firebase.google.com/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/801359/322dd04d-c91c-db5c-9557-6e6b3cd05946.png)

#### 2 .プロジェクトに名前を付ける
お好きな名前で大丈夫です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/801359/8e19aa16-a510-a2f2-830a-6d84d7eda3b7.png)

#### 3 .Google アナリティクス
Google アナリティクスは今回はオフにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/801359/d2942c43-e736-1a27-79a1-777f82512092.png)

#### 4 .android iconをクリック
android iconをクリック。
![スクリーンショット 2022-07-07 083549.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/801359/1f0a6876-39a1-3b88-fc79-7b556bb4786e.png)

#### 5 .Android パッケージ名を入力
Android パッケージ名は、androidアプリのAndroiManifest.xmlで確認できます。
その他の項目は空欄のままで大丈夫です。
![スクリーンショット 2022-07-07 083922.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/801359/ce20a06c-4b21-f24f-b646-7abdcd92fd32.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/801359/649b593f-654e-95aa-df8e-26504c8366b9.png)

#### 6 .google-services.jsonの導入
google-services.jsonとは、今作成したプロジェクトの情報が記述せれているjsonファイルです。
ダウンロードした google-services.json ファイルを Android アプリのappディレクトリー直下に配置します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/801359/88d91504-47e5-3425-701d-4f435e1b8e05.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/801359/6c7da8b4-4682-9dbd-2722-d4295694ef87.png)

#### 7 .Firebase SDK の追加
ここは後で追加するので、次へをクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/801359/495f2add-c943-02f9-a510-48bbbd091c83.png)

#### 8 .Firebaseの設定完了
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/801359/e26746ea-2a6e-15ac-e3b9-458538c4db58.png)

# Androidアプリの設定
#### 1 .プロジェクトレベルのbuild.gradleを修正
```gradle
// 追記　ここから
buildscript {
    dependencies {
        classpath 'com.google.gms:google-services:4.3.13'
    }
    repositories {
        google()
    }
}
// ここまで

plugins {
    id 'com.android.application' version '7.1.2' apply false
    id 'com.android.library' version '7.1.2' apply false
    id 'org.jetbrains.kotlin.android' version '1.6.20' apply false
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

}
```

#### 2 .アプリレベルのbuild.gradleを修正
```gradle
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'

    // 追記
    id 'com.google.gms.google-services'
}

android {
    compileSdk 32

    defaultConfig {
        applicationId "com.example.pushnotificationtest"
        minSdk 29
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {
    implementation 'androidx.core:core-ktx:1.8.0'
    implementation 'androidx.appcompat:appcompat:1.4.2'
    implementation 'com.google.android.material:material:1.6.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
    implementation 'com.google.firebase:firebase-bom:30.2.0'
    implementation 'com.google.firebase:firebase-messaging-ktx:23.0.6'

    // 2行追記
    implementation 'com.google.firebase:firebase-bom:30.2.0'
    implementation 'com.google.firebase:firebase-messaging-ktx:23.0.6'
}

```
修正したら「Sync now」をクリックします。

#### 3 .FirebaseMessagingServiceを継承したMyFirebaseMessagingServiceクラスを作成
Firebaseのガイド(https://firebase.google.com/docs/cloud-messaging/android/receive) を参考にサービスクラスを作成します。
今回はMainActivityと同じ階層に作成します。
```kotlin
class MyFirebaseMessagingService: FirebaseMessagingService() {
    private val TAG = "push notification"

    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        super.onMessageReceived(remoteMessage)
        Log.d(TAG, "onMessageReceived: " + remoteMessage.data["message"])
    }
}

```

#### 4 .AndroidManifest.xmlにサービスを登録
```xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.pushnotificationtest">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.PushNotificationTest">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <!-- 追加 ここから -->
        <service
            android:name=".MyFirebaseMessagingService"
            android:exported="false">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>
        <!-- 追加 ここまで -->
    </application>
</manifest>

```

#### 5 .MainActivityの修正
FCM token(アプリのアドレス)を取得する為のコードを記述します。
このコードを記述することで、MainActivityが作成されたタイミングでFCM tokenが表示され、自動的にクリップボードにコピーされます。
このtokenは、google keepやメールなどを使いパソコンで利用出来るようにしておいてください。

```kotlin
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        // 追加
        updateFCMToken()
    }
    
    // 追加
    private fun updateFCMToken() {
        FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
            if (!task.isSuccessful) {
                Log.w("FIREBASE", "getInstanceId failed", task.exception)
                return@OnCompleteListener
            }
            val token = task.result

            // 以下通知テスト用にFCMトークンを表示、コピーする為の記述
            Toast.makeText(baseContext,token,Toast.LENGTH_LONG).show()
            val clipboardManager = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
            val clipData = ClipData.newPlainText("text", token)
            clipboardManager.setPrimaryClip(clipData)
        })
    }
}
```
![Screenshot_20220707_090750.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/801359/d463bffb-67b7-011b-f528-f165cb923d06.png)

#### 6 .Androidアプリの設定完了
Androidアプリを実行するとPush通知が受け取れるようになります。
※ただしアプリがフォアグラウンドの時はまだ通知を受信できません。これについては、また後で説明します。

# テストメッセージを送信
Firebaseコンソール(https://console.firebase.google.com/) でテスト用の通知メッセージを作成します。

#### 1 .Cloud messageing、send your first messageを順にクリックしてメッセージ作成画面を表示します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/801359/55b9fffb-1ac5-900b-ccb7-01ed1ea5a902.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/801359/80689b41-a982-e088-86e7-027dc19ba32e.png)

#### 2 .メッセージの送信
通知のタイトル、通知テキストを記述してテストメッセージを送信をクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/801359/748d785c-46dc-d208-817f-bfd027287708.png)

FCM登録トークンを追加箇所に、先程取得したFCM tokenを貼り付け。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/801359/855ff58d-6e90-e4b8-a560-5d83fc344db7.png)
メッセージを送信するとAndroidアプリにPush通知が表示されます。
![Screenshot_20220707_092333.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/801359/30af8855-31f9-52ff-95e2-1dffd5f20e49.png)
メッセージは、アプリが起動していない場合、もしくはアプリがバックグラウンドの場合のみに受信します。

# アプリがフォアグラウンド時の通知表示実装
androidのpush通知には、2種類あります。
1つはアプリがforeground時の通知、もう1つはアプリがbackground時の通知です。
background時の通知は、OSが自動で受け取ったデータから通知を生成してくれますが、
foreground時の通知は、自分で受けっとた時の処理を書く必要があります。
より詳しく知らたい人は以下のサイトを御覧ください。
https://firebase.google.com/docs/cloud-messaging/concept-options#notifications_and_data_messages
https://note.com/kirimin_chan/n/n04fbabbf17bb

それでは、forground時に通知を受け取れるように実装していきましょう。
#### 1 .MyFirebaseMessagingService classのonMessageReceived関数の編集
以下のコードは受信したデータからtitle, bodyを取得してMainActivityに遷移する通知を作成しています。
```kotlin
    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        super.onMessageReceived(remoteMessage)
        Log.d(TAG, "onMessageReceived: " + remoteMessage.data["message"])

        // 追加　ここから
        val intent = Intent(this, MainActivity::class.java)
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
        val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_ONE_SHOT)
        val channelId = "Default"
        val builder: NotificationCompat.Builder = NotificationCompat.Builder(this, channelId)
            .setSmallIcon(R.mipmap.ic_launcher)
            .setContentTitle(remoteMessage.notification!!.title)
            .setContentText(remoteMessage.notification!!.body).setAutoCancel(true)
            .setContentIntent(pendingIntent)
        val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
        val channel = NotificationChannel(
            channelId,
            "Default channel",
            NotificationManager.IMPORTANCE_DEFAULT
        )
        manager.createNotificationChannel(channel)
        manager.notify(0, builder.build())
       // ここまで
    }
```

この記述を追加することで、アプリがforground時でも通知を表示されるようになりました。
またonMessageReceived関数の内容によっては、アプリがforground時にメッセージを受け取った場合にdialogを表示するActivityを起動することでdialogを表示することも可能です。