Tutorial: Receiving Push Notifications

Serverless DatabaseNotifications Receiving Push Notifications

Once a subscription active, receiving notifications on the client device goes in two steps:

  1. Retrieve the low-level notification payload from the messaging service (FCM for Google compliant Android devices and iOS devices, or HMS for Huawei non Google compliant Android devices),
  2. Parse the payload into a readable event, using the appropriate Webcom SDK function.

Retrieving the notification payload

Not applicable for JavaScript API

In Kotlin, the notification payload data must be retrieved as a Map<String, String>:

With FCM, you must define a subclass of FirebaseMessagingService and register it in the AndroidManifest.xml file of your application. Each time the device receives a notification, the messaging service then calls the onMessageReceived() method of this subclass:

class MyFCMService : FirebaseMessagingService() {
  // ...
  override fun onMessageReceived(message: RemoteMessage) {
    val payload: Map<String, String> = message.data
    // ...
  }
  // ...
}

With the HMS Push Kit, you must define a subclass of HmsMessageService and register it in the AndroidManifest.xml file of your application. Each time the device receives a notification, the messaging service then calls the onMessageReceived() method of this subclass:

class MyHMSService : HmsMessageService() {
  // ...
  override fun onMessageReceived(message: RemoteMessage?) {
    if (message != null) {
      val payload: Map<String, String> = message.dataOfMap
      // ...
    }
  }
  // ...
}

You must add a notification service extension target to your iOS app.

The Webcom SDK also needs to share information (Webcom application configuration and secret keys when encryption is used) between the app target and the extension target. Thereby, they must have a common App Group, configured in their respective Signing & Capabilities configuration tabs.

import UserNotifications

final class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, MessagingDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // ...
        Webcom.configure(groupName: "group.XXX.YYY.ZZZ") // same value as in didReceive(_:withContentHandler:) method of UNNotificationServiceExtension
        // ...
    }
}

final class NotificationService: UNNotificationServiceExtension {

    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        Webcom.configure(groupName: "group.XXX.YYY.ZZZ") // same value as in application(_:didFinishLaunchingWithOptions:) method of UIApplicationDelegate
        guard let notification = WebcomNotification(request: request) else {
            // not a Webcom notification
            contentHandler(request.content)
            return
        }
        // ...
    }

    override func serviceExtensionTimeWillExpire() {
        // ...
    }
}
Not applicable for REST API

Parsing the notification payload

Not applicable for JavaScript API

The Webcom SDK for Android provides a static method to parse this low-level payload into a PushNotificationEvent, and more precisely into a Notification<PushNotificationEvent>:

import com.orange.webcom.sdk.datasync.subscription.PushNotificationEvent
import com.orange.webcom.sdk.datasync.subscription.PushNotificationEvent.*

val payload: Map<String, String> = message.data // or .dataOfMap for HMS messaging service
val event = PushNotificationEvent.makeFromPushNotification(payload) // may throw (typically if deciphering fails)
println("A notification has been received from Webcom app: ${event.appId} on path: ${event.path}")
when (event) {
  is DatasyncEvent.Revoked -> println("The subscription was revoked: ${event.error.message}")
  is Value.Changed -> println("Value has changed: ${event.value}")
  is Value.ChangedHidingData -> println("Value has changed, but you have to read it explicitly")
  is Value.TooBig -> println("Value has changed, but it oversizes the notification")
  is Children.Updated -> println("Some child nodes have changed: ${event.added.size} added, ${event.changed.size} changed, ${event.removed.size} removed")
  is Children.TooBig -> println("Some child nodes have changed, but they oversize the notification")
}

Possible reasons for revocation are the same as when subscribing through webhooks.
When the received event is not a revocation, depending on which event type has been subscribed, it may be either:

Subscribed event type Possible notified event
Subscribable.Value.Changed PushNotificationEvent.Value.Changed
PushNotificationEvent.Value.TooBig
Subscribable.Value.ChangedHidingData PushNotificationEvent.Value.ChangedHidingData
Subscribable.Child.* PushNotificationEvent.Children.Updated
PushNotificationEvent.Children.TooBig

If the payload of a Value.Changed or a Children.Updated event oversizes the limitation of the messaging service, then a Value.TooBig, a Children.Updated with all values of added and changed keys set to null, or a Children.TooBig event is emitted instead, along the logic described here.

import UserNotifications

final class NotificationService: UNNotificationServiceExtension {

    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        Webcom.configure(groupName: "group.XXX.YYY.ZZZ") // same value as in application(_:didFinishLaunchingWithOptions:) method of UIApplicationDelegate
        guard let notification = WebcomNotification(request: request) else {
            // not a Webcom notification
            contentHandler(request.content)
            return
        }
        print("A notification has been received from Webcom app: \(notification.configuration.identifier) on path: \(notification.path)")
        switch notification.event {
            case let .valueChange(.value(value)):
                print("Value has changed: \(value.serialized)")
            case .valueChange(.hidden):
                print("Value has changed, but you have to read it explicitly")
            case .valueChange(.tooBig):
                print("Value has changed, but it oversizes the notification")
            case let .childrenUpdate(.full(details)):
                print("Some child nodes have changed:")
                print("\(details.additions.count) added, \(details.changes.count) changed, \(details.removals.count) removed")
            case let .childrenUpdate(.onlyKeys(details)):
                print("Some child nodes have changed, but we only have their keys (no values):")
                print("\(details.additions.count) added, \(details.changes.count) changed, \(details.removals.count) removed")
            case .childrenUpdate(.tooBig):
                print("Some child nodes have changed, but they oversize the notification")
            case let .revocation(reason: reason):
                print("The subscription was revoked: \(reason)")
        }
        // ...
    }

    override func serviceExtensionTimeWillExpire() {
        // ...
    }
}

A more complete example using notifications is available there.

Possible reasons for revocation are the same as when subscribing through webhooks.
When the received event is not a revocation, depending on which event type has been subscribed, it may be either:

Subscribed event type Possible notified event type
DataEventType.valueChange(withData: true) WebcomNotificationEvent.valueChange(.value(...))
WebcomNotificationEvent.valueChange(.tooBig)
DataEventType.valueChange(withData: false) WebcomNotificationEvent.valueChange(.hidden)
DataEventType.child(...) WebcomNotificationEvent.childrenUpdate(.full(...))
WebcomNotificationEvent.childrenUpdate(.onlyKeys(...))
WebcomNotificationEvent.childrenUpdate(.tooBig(...))

If the payload of a .valueChange event oversizes the limitation of the messaging service, then the associated value is .tooBig (instead of .value(...) in the normal case).
If the payload of a .childrenUpdate event oversizes the limitation of the messaging service, then the associated value is .onlyKeys(...) or .tooBig in last resort (instead of .full(...) in the normal case).

Not applicable for REST API

Errors with the messaging service

When the Webcom back end attempts to send a mobile push notification, the underlying relevant messaging service (namely FCM or HMS) may fail. In case of such an error from this third-party component:

  • Either it is recoverable (corresponding FCM and HMS errors: "QUOTA_EXCEEDED", "UNAVAILABLE", "INTERNAL", "UNSPECIFIED_ERROR"), then the Webcom back end tries the request again with exponential backoff starting after 1 second, and during 2 hours.
  • Or it isn't recoverable or still failing after 2 hours, then the current pending notifications are discarded but the subscription remains active for future notifications.

In case of other error (for example if the FCM or HMS token representing the device id has been revoked by the messaging service), the subscription is revoked, and if possible, a revocation notification is sent.