まえがき
私が開発しているReact Nativeアプリについて、元々react-native-firebase(v5)を使ってプッシュ通知を実装していましたが、そろそろバージョンを上げることにしました。
ところが、v6からローカル通知の機能がNotifeeという有料の別ライブラリに分離されており、ローカル通知の機能のみ他のライブラリを採用せざるを得なくなりました。
そこで使えそうなライブラリを調べてみたところ、
- Notifee: ローカル通知専用ライブラリ。react-native-firebaseのチームが作っており当然相性も良く、品質も高そうだが、有料。
- react-native-push-notification: リモート&ローカル通知ライブラリ。iOS部分はreact-native-notification-iosを使用している。一番スターが多い。
- react-native-notifications: リモート&ローカル通知ライブラリ。Wix製。Androidでchannelを指定してローカル通知を送れなさそう。(今のところ)
という感じのようだったので、react-native-push-notificationを採用することにしました。
本記事では、react-native-firebase(v6↑)とreact-native-push-notificationを使ってプッシュ通知を実装するにあたっての注意点を紹介します。
追記 (2023/05/29)
今さら気が付いたのですが、Notifeeが無料になったようですのでNotifeeを使うのが一番良いと思われます!
実現したいこと
- FCMで受信したメッセージ(※)を、アプリの状態(Foreground/Background/Quit)にかかわらずプッシュ通知で表示したい
- プッシュ通知をタップしたとき、アプリの状態にかかわらず処理したい
(※) 本記事で扱うFCMメッセージは notification
と data
の両方を含む、データペイロード付きの通知メッセージとします。
本記事で触れないこと
- react-native-firebaseの導入方法
- FCMの導入方法
環境
- React Native (0.63.2)
- @react-native-firebase/app (8.4.2)
- @react-native-firebase/messaging (7.8.6)
- react-native-push-notification (5.1.1)
- @react-native-community/push-notification-ios (1.5.0)
react-native-push-notificationを導入する
※ react-native-firebaseについては導入が済んでいるものとします。
まずは、react-native-push-notificationを導入します。このライブラリは、iOS部分については@react-native-community/push-notification-iosを使用しているため、併せてインストールを行います。
npm install --save react-native-push-notification @react-native-community/push-notification
npx pod-install
続けて、iOSについて下記コードの追記を行います。
#import <UserNotifications/UNUserNotificationCenter.h>
...
@interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate, UNUserNotificationCenterDelegate>
#import <UserNotifications/UserNotifications.h>
#import <RNCPushNotificationIOS.h>
...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;
return YES;
}
...
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler
{
[RNCPushNotificationIOS didReceiveNotificationResponse:response];
completionHandler();
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
{
completionHandler(UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge);
}
push-notification-iosのREADMEの導入手順には、他にもリモート通知用のデリゲートメソッドがいくつか記載してありますが、今回はローカル通知用として使用するため、上記のデリゲートメソッドがあればOKです。
実装前に抑えておくポイント
アプリの状態
アプリには以下の3つの状態があり、状態によってプッシュ通知受信時およびタップ時の挙動が異なります。
- Foreground: アプリが前面にあり動作している状態
- Background: ホーム画面や他のアプリが前面にあり、アプリが背面で動作している状態
- Quit: タスクキルなどでアプリが死んでいる状態
Quit時にプッシュ通知をタップした場合は、アプリが起動します。その起動したタイミングでタップしたプッシュ通知を処理することになります。
リモート通知とローカル通知
アプリがBackgroundまたはQuit状態のときにFCMを受信すると、FCM側(react-native-firebase)がプッシュ通知を表示してくれます。しかし、Foreground状態の場合には表示してくれず、代わりにonMessage
コールバックが呼ばれます。そのため、onMessage
の中で、react-native-push-notificationを使ってプッシュ通知を表示する処理を実施する必要があります。本記事では、前者のことをリモート通知、後者のことをローカル通知と呼んでいます。
実装する
関係するコードの抜粋です。
import PushNotification from 'react-native-push-notification';
import PushNotificationIOS from '@react-native-community/push-notification-ios';
class App extends Component {
constructor(props) {
...
PushNotification.configure({
requestPermissions: false,
onNotification: notification => {
// プッシュ通知をタップしたときに呼ばれる
... // URLを開くなどの処理
notification.finish(PushNotificationIOS.FetchResult.NoData);
}
});
}
componentDidMount() {
this.unsubscribeMethods = [
firebaseMessaging().onTokenRefresh((token: string) => {
// トークンリフレッシュ時に呼ばれる
... // トークンリフレッシュ時の処理
}),
firebaseMessaging().onMessage(message => {
// Foreground時にリモートプッシュ通知を受信した際に呼ばれる
this._localNotification(message);
})
];
}
componentDidUnmount() {
this.unsubscribeMethods();
}
// ローカル通知
_localNotification(message) {
PushNotification.localNotification({
title: message.notification.title,
message: message.notification.body,
userInfo: message.data,
...
});
}
}
ForegroundでFCMを受信したときにはonMessage
が呼ばれるので、localNotification()
でローカル通知を行っています。
プッシュ通知がタップされたときはreact-native-push-notificationのonNotification
が呼ばれます。ローカル通知だけでなく、react-native-firebaseが表示したリモート通知がタップされたときも呼ばれます。また、Quit時にタップされてアプリが起動する場合も、起動時に呼ばれます1。すなわち、プッシュ通知がタップされた際の処理は、あらゆる場合にonNotification
でカバーできることになります。
なお、@react-native-firebase/messagingには、リモート通知をタップした際に呼ばれるonNotificationOpenedApp
や、起動時に呼ぶことでQuit時にタップした通知を取得するgetInitialNotification()
が用意されていますが、リモート通知に限って処理を行いたいケースを除いては、特に使う必要はなさそうです。
注意点
Androidの場合かつリモート通知をタップした場合、onNotification
で通知されるnotificationの構造が異なります。
例えば、下記の形のFCMメッセージが送信された場合、
{
"message": {
"token": "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
"notification": {
"title":"お知らせ",
"body":"タップしてURLを開きます"
},
"data": {
"url": "https://natade-coco.co.jp/"
}
}
}
onNotification
で受け取ったnotificationから url
へは notification.data.url
でアクセスできます。
しかし、Androidのかつリモート通知の場合にはdata
で囲まれておらず、 notifiation.url
で直接アクセスする形になります。そのため、あらゆるケースで正しくデータを拾うためには一工夫が必要になりそうです。
タップしたときの動き(メモ)
各OS、各アプリ状態、ローカル通知/リモート通知について、プッシュ通知をタップしたときにどのコールバックが呼ばれるかを整理した際のメモを載せておきます。onNotification
では、Foregroundでタップされたかどうかの値foreground
、ユーザーがタップしたかどうかの値userInteraction
が引数で渡されますが、一部のケースで誤った値になっていそうです(該当箇所は太字で記載しています)。
iOS
ローカル通知
-
Foreground:
onNotification
(foreground=true, userInteraction=false)が呼ばれる -
Background:
onNotification
(foreground=false, userInteraction=true)が呼ばれる。 -
Quit:
onNotification
(foreground=false, userInteration=true)が呼ばれる。(getInitialNotification()
では取れない。)
リモート通知
-
Foreground:
onNotificationOpenedApp
が呼ばれる。onNotification
(foreground=true, userInteraction=false)も呼ばれる。 -
Background:
onNotificationOpenedApp
が呼ばれる。onNotification
(foreground=false, userInteraction=true)も呼ばれる。 -
Quit:
getInitialNotification()
で取れる。onNotification
(foreground=false, userInteraction=true)も呼ばれる。
Android
ローカル通知
-
Foreground:
onNotification
(foreground=true, userInteraction=true)が呼ばれる。 -
Background:
onNotification
(foreground=true, userInteraction=true)が呼ばれる。 -
Quit:
onNotification
(foreground=false, userInteration=true)が呼ばれる。(getInitialNotification()
では取れない。)
リモート通知
-
Foreground:
onNotification
(foreground=false, userInteraction=true)が呼ばれる。onNotificationOpenedApp
も呼ばれる。 -
Background:
onNotification
(foreground=false, userInteraction=true)が呼ばれる。onNotificationOpenedApp
も呼ばれる。 -
Quit:
getInitialNotification()
で取れる。onNotification
(foreground=false, userInteraction=true)も呼ばれる。
まとめ
- ForegroundでFCMを受信した際にはプッシュ通知が表示されず
onMessage
が呼ばれるので、そのタイミングでreact-native-push-notificationのlocalNotification()
を使ってローカル通知を表示する。 - OS、アプリの状態、リモート通知/ローカル通知にかかわらず、通知タップ時の処理は
onNotification
で行えばOK。 -
onNotification
のforegroundやuserInteractionの値は怪しいので、場合分けには使わない方が無難。
以上です。
-
この挙動は
configure
でpopInitialNotification
を指定すると変更できるようです。本記事ではデフォルト(true)の場合について言及しています。 ↩