プッシュ通知から任意の画面に遷移する

2024/02/06に公開1

Scorp

アプリがバックグラウンド・フォアグラウンド・タスクキル状で取得したプッシュ通知から任意の画面に遷移させるまで。

Proposal

Firebase Cloud Messagingの公式
https://firebase.google.com/docs/cloud-messaging/flutter/receive?hl=ja

以下は某アプリに落とし込んだケース。

ポイント①は「アプリ終了状態とそれ以外」でプッシュ通知の表示を制御している。
アプリ終了状態の場合は.getInitialMessage()で取得したinitialMessageを引数に、それ以外の場合は.onMessageOpenedApp()で取得したプッシュ通知を引数にポイント②の_handleMessage()を呼び出している。

このケースでは任意の画面に遷移させる且つ任意の画面タイプで表示させる仕様に対応しようとしていて、ポイント②の_handleMessage()ではプッシュ通知のデータの中からis_allというキーの値をみて遷移先の画面タイプを制御している。

ポイント③はmain.dartのMyAppクラスの中の記述となっていて、useEffect※1の中でhandleNotification()を画面表示時の最初に一度だけ走らせて、callbackRouterの返り値のisAll(ポイント②でbool値に変換した画面タイプ)を評価して、GoRouterのgoNamed()の引数extraに画面タイプを渡して遷移している。

ポイント④のGoRouterでの遷移の仕方について
タスクキル状態で取得したプッシュ通知の場合に、プッシュ通知の取得処理〜取得後の処理(ポイント①~③)のタイミングではまだcontextが発生していないため、直接Routerから遷移させる必要がある。
このプロジェクトではルーティングパッケージにGoRouterを利用していて、今回のケースではgoRouterProvider(監視先はGoRouterそのもの)を呼び出して、GlobalKey<NavigatorState>で定義したnavigatorKeyからNavigatorを管理(画面遷移など)を行なっている。

sample.dart
ポイント③
useEffect(
         () {
           _pushNotifications.handleNotification(
             callbackRouter: (bool isAll) {
               // 画面遷移前にプッシュ通知(お知らせ)データを取得し直す。
               ref.invalidate(notificationFetchProvider);
                             ポイント④
               ref.watch(goRouterProvider)
                             .goNamed(
                       SpecifiedPage.path,
                       extra: isAll ? SpecifiedPageType.all : SpecifiedPageType.private,
                         );
             },
           );

<省略>
ポイント①
Future<void> handleNotification({
       void Function(bool isAll)? callbackRouter,
     }) async {
       // アプリ終了状態で取得したプッシュ通知を取得
       final initialMessage = await FirebaseMessaging.instance.getInitialMessage();
    
       // アプリ終了状態で取得したプッシュ通知を表示
       if (initialMessage != null) {
         _handleMessage(
           message: initialMessage,
           callbackRouter: callbackRouter,
         );
       }
    
       // アプリがアプリ終了状態で以外で取得したプッシュ通知を表示
       FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
         _handleMessage(
           message: message,
           callbackRouter: callbackRouter,
         );
       });

<省略>
ポイント②
void _handleMessage({
    required RemoteMessage message,
    void Function(bool isAll)? callbackRouter,
  }) {
    if (callbackRouter != null) {
      // ※dataはString型のみだった
      final isAll = message.data['is_all'] as String?;
      callbackRouter(isAll == 'true');
    }
  }

Discussion

mashmash

ソースコードを連ねて書きすぎたので、暇な時にもう少しポイントを分けて書こう。