[Flutter/dart] アプリがbackground状態の時にFirebase Messagingの通知がタップされた場合の対応
概要
Firebase Messagingでメッセージを送信し、通知がタップされたら何らかの動作を実行する(例:特定のページに移動する)という場合は多いかと思います。
通知タップ時にアプリがforeground状態(つまりアプリを開いている)ならば簡単なのですが、アプリがbackground状態にある場合は少し工夫が必要です。
方法
FirebaseMessaging.onBackgroundMessage()ではダメなのか?
FirebaseMessagingにはonBackgroundMessage()というメソッドが用意されています。この引数(以降onBackGroundとします)に通知タップ時に実行したいメソッドを渡せばいいのでは?と思うかもしれません。ところが、以下のような問題があります。(参考)
onBackgroundMessage()が走るのは通知が来た時であって通知をタップした時ではない。 つまりonBackGround()が実行された時まだアプリは開いていないので、画面遷移のようなことはできません。
onBackGround()はトップレベル関数である必要がある。 つまり、特定のインスタンスの状態に基づいた処理は実行できません。
onBackGround()は別isolateで実行される。 dartではisolate間でメモリを共有していないので、直接main isolateの変数を書き換えることはできません。
対応案
以下のように対応します。
アプリ再開時の状態を表す変数resumeStateをトップレベルに追加。初期化時に値を0とする。 onBackGround()でresumeStateを1にする(※)。
アプリ再開時にresumeStateの値を判定。1の場合は所望の処理を実行。
(※)部において、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でアプリ再開時に何らかの動作を実行する手順は以下です。
適当なwidget(やstate)でWidgetsBindingObserverを実装する(「withは実装する」と言うのだろうか?)
didChangeAppLifecycleState()をオーバライド。このメソッドがアプリのライフサイクル変更時に呼び出される。
引数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を使用します(参考)。
具体的には以下のようにします。
SendPortでonBackGround()が呼ばれたことを表す適当な文字列を送信。
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状態の時も通知タップ時の動作を実行することができるようになります。
最新記事
すべて表示やりたいこと TextFieldで入力フォームを作りたい。 例えば入力内容が金額の場合、3桁区切りで頭に¥を付けた表記にしたい。 ただしユーザにこれらを入力させるのではなく、ユーザはあくまで数字を入力するだけで、アプリ側で自動でフォーマットしたい。 方法...
現象 やってること iosシミュレータで画像をデバイスのローカルに保存 保存したパスをデータベースに保存 アプリ立ち上げ時にデータベースから画像パスを取得し、そのパスの画像を画面上に表示 起きている現象 iosシミュレータを再起動した場合、上記3で「ファイルパスが見つからな...
やりたいこと 初期値さえ決まればあとは不変な変数がある ただし、コンストラクタ起動時にはまだ決定できない このような変数について late finalで変数を定義 (何らかのタイミングで)初期化されたかどうかをチェックし、されていなければ値を入れる(チェックしないとfina...
Comments