[Flutter/dart] アプリがbackground状態の時にFirebase Messagingの通知がタップされた場合の対応


概要

Firebase Messagingでメッセージを送信し、通知がタップされたら何らかの動作を実行する(例:特定のページに移動する)という場合は多いかと思います。

通知タップ時にアプリがforeground状態(つまりアプリを開いている)ならば簡単なのですが、アプリがbackground状態にある場合は少し工夫が必要です。



方法

FirebaseMessaging.onBackgroundMessage()ではダメなのか?


FirebaseMessagingにはonBackgroundMessage()というメソッドが用意されています。この引数(以降onBackGroundとします)に通知タップ時に実行したいメソッドを渡せばいいのでは?と思うかもしれません。ところが、以下のような問題があります。(参考


  1. onBackgroundMessage()が走るのは通知が来た時であって通知をタップした時ではない。 つまりonBackGround()が実行された時まだアプリは開いていないので、画面遷移のようなことはできません。

  2. onBackGround()はトップレベル関数である必要がある。 つまり、特定のインスタンスの状態に基づいた処理は実行できません。

  3. onBackGround()は別isolateで実行される。 dartではisolate間でメモリを共有していないので、直接main isolateの変数を書き換えることはできません。


対応案


以下のように対応します。

  1. アプリ再開時の状態を表す変数resumeStateをトップレベルに追加。初期化時に値を0とする。 onBackGround()でresumeStateを1にする(※)。

  2. アプリ再開時にresumeStateの値を判定。1の場合は所望の処理を実行。

  3. (※)部において、portを使用して別isolateの変数resumeStateの値を変更。


これにより、上記問題はすべて解決できます。

以下詳細を見ていきます。



1 アプリ再開時の状態を表す変数resumeStateをトップレベルに追加


dartの場合変数はクラスの中だけではなく、トップレベルにも定義できます。

void main() async{
  runApp(MyApp());
}

int resumeState=NOMAL_RESUME;
const NOMAL_RESUME=0;
const ON_NOTIFICATION_TAP=1;


2 アプリ再開時にresumeStateの値を判定。1の場合は所望の処理を実行。


dartでアプリ再開時に何らかの動作を実行する手順は以下です。


  1. 適当なwidget(やstate)でWidgetsBindingObserverを実装する(「withは実装する」と言うのだろうか?)

  2. didChangeAppLifecycleState()をオーバライド。このメソッドがアプリのライフサイクル変更時に呼び出される。

  3. 引数stateがAppLifecycleState.resumedがアプリ再開時なので(AndroidでのonResume()に当たる)、この場合に所望の処理が実行されるようにする。

class basePageState extends State<basePage> with WidgetsBindingObserver{

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state==AppLifecycleState.resumed){
      if (resumeState==ON_NOTIFICATION_TAP) {
        //ここに所望の処理を書く!
        resumeState=NOMAL_RESUME;
      }
    }
  }
}


3 portを使用して別isolateの変数resumeStateの値を変更。


上でも述べましたが、onBackGroundは別isolateで実行されるため、main isolateで定義されたresumeStateを直接書き換えることはできません。isolate間で情報をやり取りするためには、portを使用します(参考)。


具体的には以下のようにします。

  1. SendPortでonBackGround()が呼ばれたことを表す適当な文字列を送信。

  2. ReceivePortで上記文字列を受け取った場合は、resumeStateを1に変更する。


static const String BG_PORT_NAME="bg_port";
static const String BG_MESSAGE="bg_message";

Future<void> onBackGround(RemoteMessage message)async{
  final port=IsolateNameServer.lookupPortByName(BG_PORT_NAME);
  port.send(BG_MESSAGE);
}
void setBackGroundHandling(){
  ReceivePort backgroundMessagePort=ReceivePort();
  IsolateNameServer.registerPortWithName(
    backgroundMessagePort.sendPort, BG_PORT_NAME);
    
  backgroundMessagePort.listen((message) {
    if (message.toString()==BG_MESSAGE){
      resumeState=ON_NOTIFICATION_TAP;
    }
  });
}
void main() async{
  //・・・
  FirebaseMessaging.onBackgroundMessage(onBackGround);
  setBackGroundHandling();
  runApp(MyApp());
}

以上の対応で、アプリがbackground状態の時も通知タップ時の動作を実行することができるようになります。



最新記事

すべて表示

現象 flutter_local_notificationを使って通知を作成。通知タップ時の動作も登録。 通知が来た時にアプリが立ち上がっている(foregroundでもbackgroundでも)と、期待通りの動作となる。 しかし、通知が来た時にアプリが終了していると、通知をタップしても登録した動作とならない。 原因と解決策 アプリが終了すると登録した動作は消えてしまうらしい(参考)。 Note:

現象 FirebaseMessaging.onBackgroundMessage()でタイトルのエラーが発生。 原因と解決策 上記メソッドの引数に渡すデリゲートはトップレベル関数でなければならない。自分の場合はインスタンスメソッドを渡していたのでエラーとなった。 渡すメソッドをトップレベル関数にするとエラーは解消した。 @main.dart void main() async{ //略 F

靴を大切にしよう!靴管理アプリ SHOES_KEEP

納品:iPhone6.5①.png

靴の履いた回数、お手入れ回数を管理するアプリです。

google-play-badge.png
Download_on_the_App_Store_Badge_JP_RGB_blk_100317.png

テーマ日記:テーマを決めてジャンルごとに記録

訂正①2040×1152.jpg

ジャンルごとにテーマ、サブテーマをつけて投稿、記録できる日記アプリです。

google-play-badge.png