Firebaseを利用したプッシュ通知のカスタムデータの解釈(その1)

沖縄は梅雨に入ってしまったそうですが、関東では過ごしやすい日が続いて欲しいと思う今日この頃です。ランニング🏃‍♀️もはじめました(健康になるぞ...!!)

さて今回は、Firebaseを利用したプッシュ通知の実装について(特に、任意のデータを受け取りたい場合にどのような実装が必要かについて)書いてみました。

プッシュ通知を受け取るところまでは、ドキュメント通りに実装すれば割と簡単にできます。
しかし、任意のデータを含めてプッシュ通知を送った場合、アプリ側でどのようにデータを受け取って解釈するかという点については、あまりまとまった情報がありませんでした。

今回やりたかったことは、受信したプッシュ通知をタップした時に任意の画面へ遷移させること。
そのためには、送信時に付与した遷移先を示す任意のデータを受信時に解釈し、その後の処理を分ける必要があります。 実現するにあたって、以下が分からなくて調べたり考えたりしました。

  • どういう形式でデータが送られてくるの?
  • 通知の中身はどこで受け取れるの?
  • そのデータをどういう構造体で保持すべき?(これは次の記事)

それぞれについて、備忘録としてまとめておきます📝

どういう形式でデータが送られてくるの?

ドキュメント通知メッセージ ペイロードの解釈では以下の形式で送られてくると書かれていました。
"customKey" : "customValue" というのが任意の値ですが、任意の値も、標準で送られてくる値と同じ階層に配置されるようです。

{
  "aps" : {
    "alert" : {
      "body" : "great match!",
      "title" : "Portugal vs. Denmark",
    },
    "badge" : 1,
  },
  "customKey" : "customValue"
}

例えば"test" : "aaa"というカスタムデータを追加してfirebase consoleから送信し、実際に受け取ったデータをprintすると以下のように出力されました。

[AnyHashable("aps"): {
    alert =     {
        body = "{bodyのテキスト}";
        title = "{titleのテキスト}";
    };
}, AnyHashable("gcm.message_id"): {message_id}, AnyHashable("test"): aaa, ...(省略)]

f:id:muchan611:20200511223740p:plain

プッシュ通知を受信した際に、受け取ったデータ(dictionary)にtestというキーを指定することで値aaaを取得できます。

通知の中身はどこで受け取れるの?

プッシュ通知受信時はUIApplicationDelegateUNUserNotificationCenterDelegateの関数が呼ばれ、その中で、一緒に送られてくる任意のデータを参照することができます。
(※送られてくるデータのことを、ここからはペイロードと記述します)

呼び出される関数は、アプリの起動状態によって異なります。
具体的には、プッシュ通知を受け取る可能性がある状況は以下の3パターンで、
それぞれ呼び出される関数が異なります。
FYI: https://firebase.google.com/docs/cloud-messaging/ios/receive?hl=ja

  • A .バックグラウンドで起動している時
  • B .未起動時(プロセスが起動していない時)
  • C .フォアグラウンドで起動している時

それぞれ詳しくみていきます。

A. バックグラウンドで起動している時

UIApplicationDelegateapplication(_:didReceiveRemoteNotification:fetchCompletionHandler:) が呼び出されます。 この場合、userInfoにペイロードが含まれます。

class AppDelegate: UIResponder, UIApplicationDelegate {
  func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
      print("userInfo: \(userInfo)") //userInfoにペイロードが含まれる
      completionHandler(UIBackgroundFetchResult.newData)
  }
}

B. 未起動時(プロセスが起動していない時)

アプリが未起動の状態でプッシュ通知をタップして開くと、通常の起動時と同じでUIApplicationDelegateapplication(_:didFinishLaunchingWithOptions:) が呼び出されます。
この場合、launchOptionsに.remoteNotificationのキー名でペイロードが含まれていました。

class AppDelegate: UIResponder, UIApplicationDelegate {
  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
      if let data = launchOptions?[.remoteNotification] as? [AnyHashable: Any] {
        print("data: \(data)") //.remoteNotification を指定するとペイロードが取得可能
      }
  }
}

C. フォアグラウンドで起動している時

アプリを開いている場合は、UNUserNotificationCenterDelegateuserNotificationCenter(_:willPresent:withCompletionHandler:)が呼び出されます。
ただ、このままでは通知は表示されません。
通知のビューを表示したい場合はcompletionHandlerを実行します。

extension AppDelegate: UNUserNotificationCenterDelegate {
  func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
      completionHandler([.alert, .sound]) // 通知を表示し、通知音も鳴らす
  }
}

completionHandlerを呼び出して表示された通知をタップした場合は、UIApplicationDelegateapplication(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any])が呼び出されます。 ここでも、通知のペイロードはuserInfoに含まれています。

class AppDelegate: UIResponder, UIApplicationDelegate {
  func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
      print("userInfo: \(userInfo)") //userInfoにペイロードが含まれる
  }  
}

そのデータをどういう構造体で保持すべき?

受信できたデータを、どのような構造体で保持しておくのが良いでしょうか。
冒頭で書いた通り、画面遷移に必要な情報ペイロードに含めますが、遷移先の画面によって必要な値が異なり、ほぼ全ての値がnullableになります。
こういった場合は、どのようにデコードし、値を保持しておくべきでしょうか。
少し頭を捻ったので、こちらについても残しておきたいと思います。

少し長くなってしまいそうなので、
これについては次の記事で書いてみようと思います。

ではでは、おやすみなさい〜👋👋