LoginSignup
16
16

react-native-firebase(v6↑) + react-native-push-notificationを使ったプッシュ通知実装の注意点

Last updated at Posted at 2020-09-18

まえがき

私が開発している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メッセージは notificationdata の両方を含む、データペイロード付きの通知メッセージとします。

参照 https://firebase.google.com/docs/cloud-messaging/concept-options?hl=ja#notification-messages-with-optional-data-payload

本記事で触れないこと

  • 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について下記コードの追記を行います。

AppDelegate.h
#import <UserNotifications/UNUserNotificationCenter.h>
...
@interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate, UNUserNotificationCenterDelegate>
AppDelegate.m
#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を使ってプッシュ通知を表示する処理を実施する必要があります。本記事では、前者のことをリモート通知、後者のことをローカル通知と呼んでいます。

実装する

関係するコードの抜粋です。

app.js
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の値は怪しいので、場合分けには使わない方が無難。

以上です。

  1. この挙動は configurepopInitialNotification を指定すると変更できるようです。本記事ではデフォルト(true)の場合について言及しています。

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