NCMBのFlutter SDKを使ってプッシュ通知を送信する（Android編）

NCMBのコミュニティSDK（公式サポートは提供されていません）としてFlutter向けのSDKを提供しています。Flutter SDKはiOS/Androidはもちろんのこと、Webなどでも利用できます。つまりスマートフォンネイティブの機能は提供していません。

NCMBのプッシュ通知機能は数多くの方に使われている機能でもあり、今回はFlutter SDKを使ってどうプッシュ通知機能を使えば良いのかを紹介します。まずはAndroid編です。

[ncmb | Dart Package](https://pub.dev/packages/ncmb)

## 利用するライブラリ

上述の通り、Flutter SDKにはネイティブ機能がありませんので、プッシュ通知で利用するデバイストークンを取得する機能がありません。そこで下記のライブラリを使います。

- [push | Flutter Package](https://pub.dev/packages/push)
- [flutter_local_notifications | Flutter Package](https://pub.dev/packages/flutter_local_notifications)

## セットアップ

Androidの場合はFirebaseにてプロジェクトを作成します。そして、Cloud Messagingを有効にしてAndroid用の設定をウィザードで進めます。最終的にgoogle-services.jsonがダウンロードできます。

![3522ee8e-dc4a-4a18-8cbb-bf8815d18957.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/d1231665-d35d-51b3-ecff-b576a5d01ca7.jpeg)


また、プロジェクトの設定で新しい秘密鍵を作成、ダウンロードします。

![ae358af8-fb87-465f-9e4d-30c58b3aceb7.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/fa3226e1-3a48-100e-e9d4-58923c272a9d.jpeg)

この秘密鍵はNCMBの管理画面にてAndroid用プッシュ通知のファイルとしてアップロードしてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/007f486b-badc-948f-369f-fd6221319db0.png)


## Flutterアプリの作成

`flutter create アプリ名 --org 組織` でアプリを作成します。Androidはコードが入ったフォルダ構成などにも影響するので、指定した方が良さそうです。

## Androidの設定

### google-services.jsonの配置

`android/app/` に先ほどのgoogle-services.jsonを保存します。

### android/build.gradleの編集

以下を追加します。最新版を追加するようにしてください。バージョンは[Android プロジェクトに Firebase を追加する  |  Firebase Documentation](https://firebase.google.com/docs/android/setup?hl=ja)にて確認できます。

```
dependencies {
	// 省略
	classpath 'com.google.gms:google-services:4.3.10' // 追加
}
```

### android/app/build.gradleの編集

以下を追記します。

```
apply plugin: 'kotlin-android'
apply plugin: 'com.google.gms.google-services' // ← 追加
```

minSdkVersion を19にします。

```
minSdkVersion 19
```

依存ライブラリを追加します。

```
dependencies {
		// 省略
    implementation platform('com.google.firebase:firebase-bom:30.1.0') // ← 追加
}
```

### android/app/src/main/AndroidManifest.xmlの編集

applicationタグのandroid:nameを .Application にします。

```xml
 <application
        android:label="pushdemo"
        android:name=".Application" <!-- ← ここ -->
        android:icon="@mipmap/ic_launcher">
```

### Application.ktの作成

MainActivity.ktがあるフォルダにApplication.ktを作成します。内容は次の通りです。

```kotlin
package jp.moongift.ncmb.pushdemo // パッケージ名は変更してください

import io.flutter.app.FlutterApplication

class Application : FlutterApplication() {
    override fun onCreate() {
        super.onCreate()
    }
}
```

### ic_notification.pngの追加

プッシュ通知用のアイコンファイルを android/app/src/main/res/drawable/ 以下にic_notification.pngとして配置します。

## ライブラリの追加

`flutter` コマンドでライブラリを追加します。

```
flutter pub add ncmb
flutter pub add push
flutter pub add flutter_local_notifications
```

## main.dartの編集

### ライブラリのインポート

追加したライブラリを読み込みます。

```dart
import 'package:push/push.dart';
import 'package:ncmb/ncmb.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
```

main関数でNCMBを初期化し、その上で FlutterLocalNotificationsPlugin の初期化も行います。

```dart
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
void main() {
	NCMB("YOUR_APPLICATION_KEY",
				"YOUR_CLIENT_KEY");
  runApp(const MyApp());
}
```

### デバイストークンの登録

initStateにて新しいデバイストークンを取得した時の処理を追加します。

```dart
@override
void initState() {
	super.initState();
	Push.instance.onNewToken.listen((token) {
		setState(() {
			_deviceToken = token;
		});
		saveToken();
	});
}
```

そして saveToken 関数でデバイストークンをNCMBへ保存します。deviceTokenとdeviceTypeは必須です。

```dart
void saveToken(String token) async {
	try {
		var installation = NCMBInstallation();
		installation
			..set("badge", 0)
			..set("deviceToken", token)
			..set("deviceType", Platform.isIOS ? "ios" : "android");
		await installation.save();
	} catch (e) {
		debugPrint("エラー ${e.toString()}");
	}
}
```

ちゃんと保存できていれば、NCMBの管理画面でInstallationクラスにデータが追加されています。

![bcf1b581-f92d-453b-83c1-7858e9b13f23.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/5ee08dbf-0429-a251-3884-90bc42292ac1.jpeg)


これでプッシュ通知の送信準備が整いました。

## プッシュ通知を送信する

NCMBの管理画面でプッシュ通知を送信します。送信先としてAndroidを選んでください。

## プッシュ通知を受け取る

プッシュ通知を受け取るとイベントが呼ばれるので、initStateで定義します。

```dart
@override
void initState() {
	super.initState();
	Push.instance.onNotificationTap.listen((data) {
		// アプリは起動中で、プッシュ通知を開いた場合
	});
	Push.instance.notificationTapWhichLaunchedAppFromTerminated.then((data) {
		// アプリが終了していた場合
		if (data == null) {
			// アプリ起動時
		} else {
			// 通知を開いた場合
		}
	});
	Push.instance.onMessage.listen((message) {
		// アプリがフォアグラウンド
	});
	Push.instance.onBackgroundMessage.listen((message) async {
		// アプリがバックグラウンド
	});
}
```

messageは次のような内容になっています。dataはこの中のmessage.dataに該当します。dataはプッシュ通知の内容がMapで入っています。

- message.notification（通知オブジェクト）
	- message.notification?.title.toString() （通知タイトル）
	- message.notification?.body.toString() （通知本文）
	- message.data ペイロード

例えばmessage.dataは次のようになります。

```json
{
	"com.nifcloud.mbaas.PushId": "UgGJ9dz9KQ7mGmjZ",
	"aps": {
		"alert": {
			"title": "プッシュ通知テスト",
			"body": "これはFlutterアプリ向けのプッシュ通知です。"
		}, 
		"sound": "default"
	}
}
```

本来はこれでプッシュ通知が表示されるはずなのですが、 `message.notification?.title` と `message.notification?.body` が null になっているために表示されませんでした。解決策が分かっていないため、代替案としてローカル通知を作成します。

```dart
Push.instance.onBackgroundMessage.listen((message) async {
	final title = message.data!['title'] as String;
	final body = message.data!['message'] as String;
	if (Platform.isAndroid) {
		const androidPlatformChannelSpecifics = AndroidNotificationDetails(
				'remote_notification', 'リモート通知',
				channelDescription: 'リモートの通知です',
				importance: Importance.max,
				priority: Priority.high,
				icon: 'ic_notification',
				ticker: 'ticker');
		const NotificationDetails platformChannelSpecifics =
				NotificationDetails(android: androidPlatformChannelSpecifics);
		await flutterLocalNotificationsPlugin.show(
				0, title, body, platformChannelSpecifics,
				payload: json.encode(message.data));
	}
});
```

![screen.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/e3bfb973-edf1-100c-d0ea-7bf587eded75.png)


そして、ローカル通知を開いた時には `Push.instance.onNotificationTap` または `Push.instance.notificationTapWhichLaunchedAppFromTerminated` が呼ばれます。

```dart
Push.instance.onNotificationTap.listen((data) {
	// アプリは起動中で、プッシュ通知を開いた場合
});
Push.instance.notificationTapWhichLaunchedAppFromTerminated.then((data) {
	// アプリが終了していた場合
	if (data == null) {
		// アプリ起動時
	} else {
		// 通知を開いた場合
	}
});
```

## まとめ

Androidのプッシュ通知の場合、Firebaseでプロジェクトを作成することと、build.gradleの修正、Kotlinファイルの作成が必要です。また、なぜかプッシュ通知がそのままでは表示ができなかったため、ローカル通知を組み合わせる必要がありました。この点だけ注意してもらえれば、FlutterアプリでもNCMBを利用できます。ぜひ活用してください。

[mBaaSでサーバー開発不要！ | ニフクラ mobile backend](https://mbaas.nifcloud.com/)