4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Flutter × Firebase Cloud Messaging × AWS Lambdaでリモートプッシュ通知を送りたい(2022/08)

Posted at

はじめに

初投稿です。

Flutterでアプリ制作を行っていた際にプッシュ通知機能を実装しようとして色々調べた際、様々な情報が錯綜しており、どの記事が正しいのかが分からなくなって色んなことを試した結果、大口の沼にはまり込んでしまったため、自分用の備忘録として残します。

同じ問題に直面している人の助けとなれば幸いです。

前提条件

リモートプッシュ通知を送る方法は様々あると思いますが、今回はプロジェクトの要件として利用していたAWS Lambda(以下Lambda)からFirebase Cloud Messaging(以下FCM)を利用してAndroid端末にプッシュ通知を送っていきます。今回は、LambdaはNode.js(TypeScript)で実装します。

iOS端末にプッシュ通知を送る際は、Appleの開発者サイトやXCodeで追加設定が必要になるのですが、今回は割愛します。

この記事では以下のことを行っている前提で説明していきます。

  • AWSアカウント・Firebaseアカウント作成済み
  • Flutterプロジェクト作成済み

事前準備

Firebase

Firebaseの準備から行います。Firebaseのプロジェクトをまだ作成していない場合は、新たなプロジェクトを作成しましょう。やり方は名前を付けて、アナリティクスの構成を決定するだけなので割愛します。

作成が完了すると、このような画面が表示されます。
firebase_project_top.png

プロジェクトにアプリを追加

作成したFirebaseのプロジェクトに、Androidアプリを追加します。次の画像の赤枠で囲まれたアイコンをクリックします。
firebase_project_add_android.png

FlutterプロジェクトでAndroidのパッケージ名を確認・入力し、「アプリを登録」をクリックします。
firebase_project_add_android_app_2.png

すると構成ファイルをダウンロードできるようになるため、ダウンロードしてFlutterのプロジェクトに追加します。構成ファイルは、/[project_root]/android/appの中に保存します。

③のステップはスキップしてこれで追加完了です。

iOSアプリも同時に行う場合は、プロジェクトのトップからiOSアプリの追加をクリックし、同じ手順を踏みます。構成ファイルは、/[project_root]/ios/Runnerの中に保存します。

サービスアカウント

続いてFirebaseをLambdaで使えるように設定を行います。Firebaseのプロジェクトトップページの左側のメニュー上部から、「プロジェクトの概要」の右にある歯車アイコンをクリックし、「プロジェクトの設定」をクリックします。
firebase_project_service_account.png

「サービスアカウント」のタブをクリックし、「新しい秘密鍵の生成」をクリックします。
firebase_project_config.png

よく読んで、「キーを生成」をクリックします。
firebase_project_config_caution.png

するとjsonファイルがダウンロードされますので、これをNode.jsプロジェクトのルートに保存しましょう。

Flutterの設定

Flutter側でプッシュ通知を有効にするためにFlutter側でも設定を行います。「FlutterFire」を利用すると必要な設定を自動で行ってくれるため、今回はこちらで設定を進めます。実装時は以下の公式ドキュメントを参考にしながら実装しました。
https://firebase.flutter.dev/docs/overview/

FlutterFire

FlutterFireとは、その名の通りFlutterとFirebaseを繋ぎ合わせるためのFlutter用のプラグインです。

FlutterFireの導入

FlutterFireを利用するにはfirebase_coreプラグインを導入する必要があるため、コマンドプロンプトやターミナルでFlutterプロジェクトフォルダに移動してから、以下のコマンドを実行します。

flutter pub add firebase_core

FlutterFireの初期化

FlutterFireを初期化するためには、FlutterFire CLIを使います。FlutterFire CLIはFirebase CLIに依存しているため、Firebase CLIをインストールする必要があります。Firebase CLIをまだ導入していない場合は以下のドキュメントに従って導入してください。
https://firebase.google.com/docs/cli

FlutterFireを導入します。コマンドプロンプトやターミナルで以下のコマンドを実行します。

dart pub global activate flutterfire_cli

インストールが完了したら、プロジェクトフォルダで以下のコマンドを実行します。

flutterfire configure

実行すると、先程設定したFirebaseのプロジェクトが表示されますので選択します。選択後、どのプラットフォームで設定を行うかを問われますので、必要なプラットフォームを選択して、続行します。

設定が完了するとfirebase_options.dartというファイルが作成されているはずです。これを/[project_root]/lib/main.dartに追記します。

まずはfirebase_corefirebase_options.dartをインポートして

import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';

メイン関数を修正します。

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  // 中略..

  runApp(MyApp());
}

FlutterFireを初期化出来ない場合

flutterfire configureのコマンドが失敗してしまう場合などは、Firebase CLIでしっかりログインが出来ているかどうかを確認してみてください。

プッシュ通知の実装(Flutter)

こちらを参考に実装して行きます。
https://firebase.flutter.dev/docs/messaging/usage

まずは、firebase_messagingのプラグインを追加します。

flutter pub add firebase_messaging

/[project_root]/lib/main.dartにそのまま通知の処理を書いていきます。

import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart'; // 追加
import 'firebase_options.dart';

// 中略..

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  // 中略..

  // FCMから通知を受け取るための処理
  FirebaseMessaging messaging = FirebaseMessaging.instance; // 追加
  // 通知の許可をもらう
  NotificationSettings settings = await messaging.requestPermission(
    alert: true,
    announcement: false,
    badge: true,
    carPlay: false,
    criticalAlert: false,
    provisional: false,
    sound: true,
  ); // 追加
  print('User granted permission: ${settings.authorizationStatus}'); // 追加
   
  runApp(MyApp());
}

Foreground, Background, Terminated

メッセージを受け取る際、アプリが最前面に表示されている時(Foreground)、アプリが裏で待機している時(Background)、アプリが終了している時(Terminated)でそれぞれの処理を書かなければいけないようです。

Foregroundメッセージ

アプリがForeground状態のときは、onMessageストリームを使ってメッセージを受け取ります。通知の許可をもらう処理の後に以下を追記します。

FirebaseMessaging.onMessage.listen((RemoteMessage message) {
  print('Got a message whilst in the foreground!');
  print('Message data: ${message.data}');

  if(message.notification != null) {
    print('Message also contained a notification: ${message.notification}');
  }
  // 中略..
  // メッセージを受け取った際に行いたい処理等
});

デフォルトでは Foreground時にメッセージを受け取っても通知は表示されません。 Foreground時にメッセージを受け取った際にもプッシュ通知を表示したい場合は、flutter_local_notificationsプラグイン等を使ってローカルプッシュ通知を表示させるように実装を行います。実装する場合は、以下を参考に実装してみてください。
https://firebase.flutter.dev/docs/messaging/notifications/#foreground-notifications

Background, Terminatedメッセージ

アプリがBackgroundまたはTerminatedの状態のときはonBackgroundMessageハンドラを利用します。ドキュメントによると、ハンドラは以下の条件を満たしていなければならないようです。

  • アノニマス関数でない
  • top-level関数である(初期化が必要なクラス関数等ではない)

main関数の外とForegroundメッセージの処理の後に処理を追記します。

// 追加
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  // Firestore等の他のFirebaseのサービスを使う場合は、initializeApp()で初期化を行う。
  await Firebase.initializeApp();
  print("Handling a background message: ${message.messageId}");

  // 中略..
  // メッセージを受け取った際に行いたい処理等
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  // 中略..

  // FCMから通知を受け取るための処理
  FirebaseMessaging messaging = FirebaseMessaging.instance;
  // 通知の許可をもらう
  NotificationSettings settings = await messaging.requestPermission(
    alert: true,
    announcement: false,
    badge: true,
    carPlay: false,
    criticalAlert: false,
    provisional: false,
    sound: true,
  );
  print('User granted permission: ${settings.authorizationStatus}');

  FirebaseMessaging.onMessage.listen((RemoteMessage message) {
    print('Got a message whilst in the foreground!');
    print('Message data: ${message.data}');

    if(message.notification != null) {
      print('Message also contained a notification: ${message.notification}');
    }
    // 中略..
    // メッセージを受け取った際に行いたい処理等
  });

  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); // 追加

  runApp(MyApp());
}

Foregroundメッセージとは違い、Background, Terminatedメッセージはメッセージを受け取ったタイミングでプッシュ通知が端末に表示されます。

FCM Token

サーバーがどの端末にプッシュ通知を送信するかを特定するために、端末側でトークンを発行する必要があります。通知の許可をもらった後に以下の処理を追記します。

String? token = await messaging.getToken();
print(token);

実際のサービスでは、トークンはユーザーの認証を行い(必要であれば)、サーバー側に送ってデータベースへ保存しておく必要がありますが、今回はデモなので、トークンをログに出力しておきます。このトークンは後ほどLambda側の実装で使います。

プッシュ通知の実装(Lambda)

続いてLambda側の実装に入ります。まずはFirebase Admin Node.js SDKを導入しましょう。
コマンドプロンプト・ターミナルでNode.jsのプロジェクトのルートへ移動し、コマンドを実行します。

npm i firebase-admin

/[project-root]/src/FirebaseCloudMessagingService.tsを作成します。また、事前準備サービスアカウントでダウンロードしたjsonファイルを読み込みます。FCM Tokenも忘れずに記載します。

import admin from "firebase-admin";
import firebaseAccountCredentials from "../service-account.json";

class FirebaseCloudMessagingService {
  constructor() {
    // adminが既に初期化されている場合は初期化しないようにする
    if(admin.apps.length === 0) {
      const serviceAccount = firebaseAccountCredentials as admin.ServiceAccount;
      admin.initializeApp({
        credential: admin.credential.cert(serviceAccount),
      });
    }
  }

  public async sendNotification() {
    const res = await admin.messaging().send({
      token: "{Flutterで取得したFCM Token}",
      notification: {
        title: "Hello, World!",
        body: "リモートプッシュ通知です!",
      },
      android: {
        priority: "high",
      },
      apns: {
        payload: {
          aps: {
            contentAvailable: true,
          },
        },
        headers: {
          "apns-push-type": "background",
          "apns-priority": "5", // Must be `5` when `contentAvailable` is set to true.
        },
      },
    });
    console.log(res);
  }
}

export default FirebaseCloudMessagingService;

androidだのapnsだの訳のわからないプロパティがくっついていますが、これはそれぞれAndroidとiOSで認識されるプロパティのようで、メッセージを重要性の高いメッセージとして認識させるための記述のようです。

今回はmessagingsend関数を使いましたが、sendMultiCastという関数もあり、これは複数のトークンに対して同じメッセージを一括で送信することができます。tokenというプロパティをtokensに変更して、配列でトークンを指定してあげると使えます。

では、Lambdaが実行されたらメッセージが送られるように実装して行きます。/[project-root]/src/index.tsを実装して行きます。

import { APIGatewayProxyCallbackV2, APIGatewayProxyEventV2, Context } from "aws-lambda";
import FirebaseCloudMessagingService from "./FirebaseCloudMessagingService";

export async function handler(event: APIGatewayProxyEventV2, context: Context, callback: APIGatewayProxyCallbackV2): Promise<any> {
  FirebaseCloudMessagingService fcm = new FirebaseCloudMessagingService();
  await fcm.sendNotification();

  callback(null, {
    statusCode: 200,
    body: JSON.stringify({
      message: "Message has been sent",
    }),
  });
}

これでLambdaが実行されれば、Flutterアプリに"Hello, World!"というタイトルでプッシュ通知が届くはずです。

終わりに

以上がLambdaを使ってFCMを介しFlutterにプッシュ通知を送る実装になります。実際のサービスレベルではまだまだ必要な実装が多いと思いますが、リモートプッシュ通知を送るために何が必要になるのか等、この記事で知ってもらえたら幸いです。

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?