0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Flutter】Firebase MessaigingでPush通知を実装する

Last updated at Posted at 2024-09-03

背景

FlutterでFirestoreのPush通知を実装する機会があり、大変複雑だったため備忘録として記録します。

目標

iOS、Androidで以下の状態で動作することを目標に設定しました。
・フォアグラウンド、バックグラウンド、アプリをキルした状態で通知が受け取れる
・通知受け取り時に通知バナーが表示される

事前設定

事前に以下の設定を行っておきます。(iOS向け)
・Apple Developerのidentifier>CapabilityにPush Notificationを追加
・Apple DeveloperからKeyファイルを作成、ダウンロード(.p8ファイル)
・Firebaseのプロジェクトの設定>Cloud Messaging>Apple アプリの構成 > APNs 認証キー へKeyファイルをアップロード

パッケージのインストール

pubspec.yamlに使用するパッケージを入力し、保存します。

dependencies:
    firebase_messaging: ^15.0.1
    flutter_local_notifications: ^17.1.2

※flutter_local_notificationsはAndroidのフォアグラウンド通知で使用します。

ターミナルで下記のコマンドを実行します。

flutter pub get

iOSの設定

XCode

Runner > Target Runner > Signing & Capabilities で+Capabilityボタンから、以下を追加します。
・Push Notifications
・Background Modes

Background Modesは、以下にチェックを入れてください。
・Background fetch
・Remote notifications
image.png

Androidの設定

・Manifest.xmlの設定は以下の通りです。

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 追加 -->
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> 
    <!-- 追加 -->

iOS、Androidバックグラウンド 実装

main.dart

@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Firebase初期化処理
  await Firebase.initializeApp();

  // バックグラウンド通知の設定
  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

  runApp(
    const ProviderScope(
      child: App(),
    ),
  );
}

Push通知を管理するクラス

※AppPushMessageは独自で定義したPush通知のメッセージを受け取るモデルです。FCMとLocalNotificationで受け取る型が違うので、AppPushMessageに変換して使用しています。

class PushNotificationRepository {
  /// FCM Tokenの取得
  Future<String?> getFcmToken() async {
    final fcmToken = await FirebaseMessaging.instance.getToken();
    return fcmToken;
  }

  /// Push通知の許可設定
  Future<void> requestPermission() async {
    // フォアグラウンドで通知が表示されるオプションの設定
    await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
      alert: true,
      badge: true,
      sound: true,
    );

    await FirebaseMessaging.instance.requestPermission(
      alert: true,
      announcement: false,
      badge: true,
      carPlay: false,
      criticalAlert: false,
      provisional: false,
      sound: true,
    );
  }  
  
  /// Push通知初期設定
  Future<void> initializePushNotification({
   // Push通知タップ後の処理
    required void Function(AppPushMessage) onTapMessage,
  }) async {
    // 通知許可のダイアログを表示
    requestPermission();
  }

  /// バックグラウンド
  Future<void> onMessageOpenedApp({
   // Push通知タップ後の処理
    required void Function(AppPushMessage) onTapMessage,
  }) async {
    FirebaseMessaging.onMessageOpenedApp.listen((message) {
      final appPushMessage = _convertRemoteMessage(message);

      onTapMessage(appPushMessage);
    });
  }

  /// アプリが起動していない状態
  Future<void> onTerminatedMessage({
    // Push通知タップ後の処理
    required void Function(AppPushMessage) onTapMessage,
  }) async {
    final remoteMessage = await FirebaseMessaging.instance.getInitialMessage();

    if (remoteMessage != null) {
      final appMessage = _convertRemoteMessage(remoteMessage);
      onTapMessage(appMessage);
    }
  }
}

AppPushMessage _convertRemoteMessage(RemoteMessage message) {
  return AppPushMessage(
    hash: message.hashCode,
    title: message.notification?.title ?? '',
    message: message.notification?.body ?? '',
  );
}

AppPushMessage _convertNotificationResponse(
    NotificationResponse response) {
  return AppPushMessage(
    hash: response.id ?? 0,
  );
}

Pusu通知の初期化設定

  Future<void> initializePushNotification(
      {Function(AppPushMessage)? onTapMessage}) async {

    // FCMトークン取得
    await pushNotificationRepository.getFcmToken();

    // iOS,Androidバックグラウンド設定   
    pushNotificationRepository.onMessage(onTapMessage: dispatchOnMessage);
   
    // アプリがキルされている状態設定   
    pushNotificationRepository.onTerminatedMessage(
      onTapMessage: onTapMessage,
    );
  }
}

Androidのフォアグラウンド通知

Androidでフォアグラウンド通知を受信するには、別途設定が必要です。

ローカル通知を管理するクラスを追加

class LocalNotificationManager {

  AndroidNotificationChannel androidNotificationChannel =
      const AndroidNotificationChannel(
    'channel_id',
    'channel_name',
    importance: Importance.max,
  );

  /// ローカル通知の初期化設定
  Future<void> initialLocalNotification({
    required AppPushMessage Function(NotificationResponse)
        convertToAppPushMessage,
    required void Function(AppPushMessage) onTapMessage,
  }) async {
    const initializationSettingsAndroid =
        AndroidInitializationSettings('@mipmap/ic_notification');
    const initializationSettingsIos = DarwinInitializationSettings();

    const initializationSettings = InitializationSettings(
      android: initializationSettingsAndroid,
      iOS: initializationSettingsIos,
    );

    await FlutterLocalNotificationsPlugin().initialize(
      initializationSettings,
      onDidReceiveBackgroundNotificationResponse: (message) {
        final appPushMessage = convertToAppPushMessage(message);
        onTapMessage(appPushMessage);
      },
    );

    await FlutterLocalNotificationsPlugin()
        .resolvePlatformSpecificImplementation<
            AndroidFlutterLocalNotificationsPlugin>()
        ?.createNotificationChannel(androidNotificationChannel);
  }

  /// 通知を表示
  Future<void> showPushNotification(AppPushMessage appPushMessage) async {
    await FlutterLocalNotificationsPlugin().show(
      appPushMessage.hash, // プッシュ通知のハッシュ値
      appPushMessage.title, // プッシュ通知のタイトル
      appPushMessage.message, // プッシュ通知のメッセージ
      NotificationDetails(
        android: AndroidNotificationDetails(
          androidNotificationChannel.id,
          androidNotificationChannel.name,
          icon: '@mipmap/ic_notification',
        ),
      ),
    );
  }
}

プッシュ通知を管理するクラスにLocalNotificationManagerの初期化処理と、フォアグラウンド通知を受け取る処理を追加

class PushNotificationRepository {
  ...
  
  final FlutterLocalNotificationsPlugin localNotificationManager;
  /// Push通知初期設定
  Future<void> initializePushNotification({
   // Push通知タップ後の処理
    required void Function(AppPushMessage) onTapMessage,
  }) async {
    // 通知許可のダイアログを表示
    requestPermission();

    // ローカル通知の初期化 ←追加
    localNotificationManager.initialLocalNotification(
      // convertToAppPushMessageにLocalNotificationで受け取ったRemoteMessageをAppPushMessage(独自のモデル)に変換する処理
      convertToAppPushMessage: _convertNotificationResponse,
      onTapMessage: onTapMessage,
    );
    
  }
  
  ...

  /// Androidのフォアグラウンド通知
  Future<void> onMessage({
   // Push通知タップ後の処理
    required void Function(AppPushMessage) onTapMessage,
  }) async {
    FirebaseMessaging.onMessage.listen((message) async {
      final appPushMessage = _convertRemoteMessage(message);
      AndroidNotification? android = message.notification?.android;

      if (android != null) {
        await localNotificationManager.showPushNotification(appPushMessage);
      }
    });
  }
  
  ...

}

プッシュ通知の初期化処理にAndroidフォアグラウンド設定を追加する。

  Future<void> initializePushNotification(
      {Function(AppPushMessage)? onTapMessage}) async {

    // FCMトークン取得
    await pushNotificationRepository.getFcmToken();

    // Androidフォアグラウンド設定    ←追加
    pushNotificationRepository.initializePushNotification(
      onTapMessage: onTapMessage!,
    );
    
    // iOS,Androidバックグラウンド設定   
    pushNotificationRepository.onMessage(onTapMessage: dispatchOnMessage);
    
    // Androidフォアグラウンド設定     ←追加
    pushNotificationRepository.onMessageOpenedApp(onTapMessage: (message) {
      onTapMessage(message);
    });
    
    // アプリがキルされている状態設定   
    pushNotificationRepository.onTerminatedMessage(
      onTapMessage: onTapMessage,
    );
  }
}

呼び出し

作成したinitializePushNotificationの初期化処理をApp.dartなどで呼び出すと、Push通知のタップ後の処理も設定することができます。
(クラスは省略)

initializePushNotification(onTapMessage: (message) {
 // Push通知タップ後に実行したい処理を渡す
  print('プッシュ通知が呼ばれたよ');
});

Android:バックグラウンド状態で通知バナーを表示する様にする

そのままの設定ではアプリキルの状態で通知を受け取った際にAndroidでは通知バナーが表示されないので設定を追加します。

AndroidのManifest.jsonの設定

<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_notification" /> ←通知アイコン

<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="@string/default_notification_channel_id"/> ←デフォルトの通知IDを定義

build.gradle

Manifest.jsonで定義したdefault_notification_channel_idにlocalNotificationで設定したチャネルIDを設定します。
以下の設定で、バックグラウンド状態でも通知バナーが表示されるようになります。

    defaultConfig {
        ...
        // localNotificationで設定したチャネルIDを設定する
        resValue "string", "default_notification_channel_id", "channel_id"
        ...   
    }

参考記事

Flutter で Firebase Cloud Messaging クライアント アプリを設定する

Flutter × FCMでプッシュ通知を実装する

Firebase Notifications & Flutter Local Notifications

告知

最後にお知らせとなりますが、イーディーエーでは一緒に働くエンジニアを
募集しております。詳しくは採用情報ページをご確認ください。

みなさまからのご応募をお待ちしております。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?