Flutter×Amplify環境において、Android端末にPush通知を送信する手順を記載する。

Flutter×AmplifyでPush通知を送信するための手順については公式ドキュメントでも解説されているが、初期設定からPush通知の受信まで、一気通貫でわかりやすく解説されている訳ではない。

そのため、実装を行なった私が一連の流れを詳しく解説しようと思う。

本記事のゴール

本記事は、AWS Pinpointでテストメッセージを送信し、デバイスでPush通知が受信できることをゴールとする。以下の手順に沿って設定、実装を行えば現時点では正常に処理を行うことができる。

  1. Firebase Cloud Messaging 設定
    • Firebaseプロジェクトの作成
    • アプリを Firebase に登録する
    • Firebase 構成ファイルをFlutterプロジェクトに追加する
    • Google services pluginをアプリに追加する
    • ServerKeyを発行する
  2. Amplifyバックエンドリソースを追加する
  3. Flutter 実装
    • ライブラリ「amplify_push_notifications_pinpoint」を追加する
    • FlutterでPush通知の初期化処理を作成する(デバイストークンを受信する)
    • FlutterでPush通知の権限をリクエストする
  4. AWS Pinpointからテストメッセージを送信し、デバイスで受信する

1. Firebase Cloud Messaging 設定

Android端末でPush通知を受信するためには、FCM側でいくつかの設定を行う必要がある。

1-1. Firebaseプロジェクトの作成

以下の公式ドキュメントを参照して、プロジェクトを作成する。
公式ドキュメントにはプロジェクトを作成した後の記述もあるが、ひとまずプロジェクトを作成するだけで良い。

Firebase公式ドキュメント: Firebase プロジェクトを作成する

1-2. アプリを Firebase に登録する

以下の公式ドキュメントに沿って設定を行う。アプリのニックネームデバッグ用の署名証明書 SHA-1については未入力で構わない。

Firebase公式ドキュメント: アプリを Firebase に登録する

1-3. Firebase 構成ファイルをFlutterプロジェクトに追加するアプリを Firebase に登録する

Firebase公式ドキュメント: Firebase 構成ファイルを追加する

アプリを Firebase に登録すると、Firebase 構成ファイル(google-services.json)をダウンロードできるようになる。ローカルにダウンロードする。

構成ファイルをFlutterアプリの<root>/android/app/に配置する。

次に、google-services.json 構成ファイルの値に Firebase SDK からアクセスできるようにする。

android/build.gradle に以下の記述を追加する。

classpath 'com.google.gms:google-services:4.3.15'

1-4. Google services pluginをアプリに追加する

次に、android/app/build.gradleに、Google サービス プラグインを追加する。
ここはFirebaseの公式ドキュメントではなく、Amplify公式ドキュメントの設定に準ずる。

以下の記述を追記する。

dependencies {
    // Import the BoM for the Firebase platform
    implementation platform('com.google.firebase:firebase-bom:32.1.0')

    // Add the dependencies for the Firebase Cloud Messaging and Analytics libraries
    // When using the BoM, you don't specify versions in Firebase library dependencies
    implementation 'com.google.firebase:firebase-messaging-ktx'
    implementation 'com.google.firebase:firebase-analytics-ktx'
}

1-5. ServerKeyを発行する

こちらはAmplify公式ドキュメントの通りに設定すれば問題ない。以下を参照。

Setting Up FCM for Push Notifications > Next, you will need to access your ServerKey (Referred to as ApiKey in the CLI setup):

2. Amplifyバックエンドリソースを追加

こちらもAmplify公式ドキュメントの通りに設定すれば問題ない。

CLIでの設定の最後に、Server Keyを入力する必要があるため、上記で発行したServerKeyを入力することで設定が正常に完了する。

Amplify公式ドキュメント: Set up backend resources

3. Flutter 実装

ここまでの設定でAndroid端末にPush通知するための枠組みは構築できた。ここからはFlutterアプリの中でPush通知に必要な機能を実装していく。

1. ライブラリ「amplify_push_notifications_pinpoint」を追加する

amplify_push_notifications_pinpointとは、Flutter×Amplify環境で「AWS Pinpoint」を使用するたのプラグイン。

pubspec.yaml にamplify_push_notifications_pinpointを追記する。

amplify_push_notifications_pinpoint: ^1.0.0

以下を実行する。

flutter pub get

これでFlutterアプリ内でamplify_push_notifications_pinpointを使用できるようになる。

Amplify公式ドキュメント: Install Amplify Libraries

2. FlutterでPush通知の初期化処理を作成する
(デバイストークンを受信する)

main.dartで以下の2つの処理を実行する。

  1. プラグインをアプリに追加する
  2. デバイストークンを取得する

プラグインをアプリに追加

こちらは以下を参照することで実現できる。以下の箇所が該当する。

Amplify公式ドキュメント: Initialize Amplify Push Notifications

final authPlugin = AmplifyAuthCognito();
final pushPlugin = AmplifyPushNotificationsPinpoint();
await Amplify.addPlugins([authPlugin, pushPlugin]);
await Amplify.configure(amplifyconfig);

デバイストークンを取得する

Push通知をユーザーのデバイスに送信するためには、デバイストークンを取得する必要がある。(デバイストークンはPush通知の送信先として使用されるため)

デバイストークンの取得処理については以下を参照する。

Amplify公式ドキュメント: デバイストークンの受信

実際の処理

以下は、実際に私がアプリケーションで実装したコード。ここではデバイストークンを取得するまでで、サーバーへの送信などの処理は行なっていない。

/*
  デバイストークンを取得してAWS Pinpointに登録する処理
  アプリ起動時に実行する
*/
Future<void> setupPushNotifications() async {
  try {
    final subscription =
        Amplify.Notifications.Push.onTokenReceived.listen((token) {
      safePrint("Device token: $token");

      // 必要に応じてサーバーサイドやデータベースにトークンを保存する処理を追加
    });

    // 不要になったときに購読をキャンセルする
    // 今回の例ではアプリがアクティブである間はキャンセルしていないが、必要に応じてキャンセルする。
    // subscription.cancel();
  } catch (e) {
    safePrint("Error Device token: $e");
  }
}

上記で作成した処理を、main.dart内で実行する。_initializeApp()を参照。

ここでのポイントは、_initializeApp()関数内でAmplifyの初期化が完了した後にsetupPushNotifications()を実行していること。

Amplifyライブラリを初期化し、アプリケーションとAmplifyバックエンドとの間の接続を確立する。この初期化が正しく行われていないと、プラグイン(この場合はプッシュ通知プラグイン)が期待するように動作しない可能性があるため。

void main() async {
  runApp(MultiProvider(
    providers: [
      ChangeNotifierProvider(create: (context) => NavigationProvider()),
      ChangeNotifierProvider(create: (context) => UserProvider()),
    ],
    child: const MyApp(),
  ));
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  MyAppState createState() => MyAppState();
}

class MyAppState extends State<MyApp> {
  // ユーザーのログイン状態を保持する変数を追加
  bool? _isUserSignedIn;

  @override
  void initState() {
    super.initState();
    _initializeApp();
  }

  _initializeApp() async {
    // Amplifyを初期化する
    await _configureAmplify();
    // Amplifyの初期化が完了した後にsetupPushNotificationsを呼び出す
    // デバイストークンを取得する
    await setupPushNotifications();
    // ログイン状態の確認を行う
    await _checkSignInStatus();
  }

  // ユーザーがログインしているかどうかを確認する関数
  Future<void> _checkSignInStatus() async {
    bool isSignedIn = await isUserSignedIn();
    setState(() {
      // ログイン状態を更新
      _isUserSignedIn = isSignedIn;
    });
  }

  // アプリケーションで Amplify を初期化する処理
  Future<void> _configureAmplify() async {
    // 利用するプラグインを追加する
    final authPlugin = AmplifyAuthCognito();
    final apiPlugin = AmplifyAPI(modelProvider: ModelProvider.instance);
    final pushPlugin = AmplifyPushNotificationsPinpoint();
    await Amplify.addPlugins([authPlugin, apiPlugin, pushPlugin]);

    // アプリ内で configure を呼び出す
    try {
      await Amplify.configure(amplifyconfig);
    } on AmplifyAlreadyConfiguredException {
      safePrint(
          "Tried to reconfigure Amplify; this can occur when your app restarts on Android.");
    }
  }

上記のコードの場合、デバイストークンの取得処理はアプリ起動時に実行されるため、アプリを起動したタイミングで以下のようにログが出力される。

Device token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

3. FlutterでPush通知の権限をリクエストする

上記の処理でデバイストークンを取得することができたが、次はユーザーのデバイスがPush通知を許可しているかどうかを判定し、権限をリクエストする処理を実装する必要がある。

Amplify公式ドキュメント: Request permissions

私は権限リクエストの処理を以下のように実装した。
詳しいステータスなどについてはコメントを参照。

import 'package:amplify_flutter/amplify_flutter.dart';

/*
  許可ステータスを取得する処理。ステータスは以下4種類
  
  1. PushNotificationPermissionStatus.shouldRequest
    権限はまだ要求されていない状態
  2. PushNotificationPermissionStatus.shouldExplainThenRequest
    権限をリクエストする前に、ユーザーに説明を表示する必要がある。
  3. PushNotificationPermissionStatus.granted
    権限はユーザーによって付与されている。それ以上のアクションは必要なく、アプリは通知を表示する準備ができている。
  4. PushNotificationPermissionStatus.denied
    権限はユーザーによって拒否されている。さらに権限を要求しようとしても、権限ダイアログは表示されなくなる。
    デバイス設定で必要な権限を付与するようユーザーに求める必要がある状態。
*/
Future<void> getPermissionStatus() async {
  final PushNotificationPermissionStatus status =
      await Amplify.Notifications.Push.getPermissionStatus();
  safePrint('push notification permission status: $status');
  handlePermissions(status);
}

/*
  許可ステータスに応じた対応 
*/
Future<void> handlePermissions(PushNotificationPermissionStatus status) async {
  switch (status) {
    case PushNotificationPermissionStatus.granted:
      // 権限はユーザーによって付与されているため、何もしなくていい
      break;
    case PushNotificationPermissionStatus.denied:
      // 権限はユーザーによって拒否されている。そもそも通知されない。
      break;
    case PushNotificationPermissionStatus.shouldRequest:
      /*
        ユーザーに権限をリクエストする
        sound: true に設定すると、通知に応じてサウンドを再生する機能を要求する。
        badge: true に設定すると、アプリのバッジを更新する機能を要求する。
      */
      await Amplify.Notifications.Push.requestPermissions(
        badge: true,
        sound: true,
      );
      break;
    case PushNotificationPermissionStatus.shouldExplainThenRequest:
      // 権限をリクエストする前にユーザーに説明する
      safePrint('TODO: 権限リクrスト前にユーザーに説明する必要がある。');
      // then request permissions
      await Amplify.Notifications.Push.requestPermissions();
  }
}

上記のgetPermissionStatusをログイン後や特定の画面に遷移したタイミングで実行することで、以下のよに端末で通知を許可するかどうかのダイアログが表示される。

私の端末の場合、ステータスがPushNotificationPermissionStatus.shouldRequestであるため、権限を確認するためのダイアログが表示された。

一度通知を許可すると、ステータスがPushNotificationPermissionStatus.grantedに変わるため、2回目以降はダイアログが表示されなくなる。

4. AWS Pinpointからテストメッセージを送信し、デバイスで受信する

ここまでの実装でデバイスがPush通知を受信するための準備は完了した。あとは、AWS Pinpointからテストメッセージを送信し、デバイスで受信できるかを確認する。

4-1. Flutterアプリを実機インストールする

シュミレーターでは挙動が不安定なケースが多いので、実機にて動作検証を行う。

AndroidのUSBデバックを有効にする。

以下の記事がわかりやすいので参照する。

Flutterアプリを実行(デバック)する。

VSCodeでアプリを実行する。

USBデバックが完了している場合、接続している実機が選択できるようになるので、選択する。

※デバイスの設定で通知を有効にしていないとPush通知を受信できないため、有効にしておく。

AWS Pinpointでテストメッセージを送信する

以下を参照し、手順通りに進めればOK。

デバイストークンは「2. FlutterでPush通知の初期化処理を作成する(デバイストークンを受信する)」で取得したものを使用する。

AWS 公式ドキュメント: テストプッシュ通知の送信

AWS Pinpointでテストメッセージを送信すると、以下のようにデバイスでPush通知を受信することができた。

カテゴリー: awsFlutter