Once a subscription active, receiving notifications on the client device goes in two steps:
- 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),
- Parse the payload into a readable event, using the appropriate Webcom SDK function.
Retrieving the notification payload
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() {
// ...
}
}
Parsing the notification payload
As you may receive notifications coming from other providers, the Webcom SDK for Android provides a static method to check whether the received notification is originating from Webcom:
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
if (PushNotificationEvent.isWebcomPushNotification(payload)) {
println("A webcom notification has been received")
} else {
println("A notification from another provider has been received")
}
Then 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
if (PushNotificationEvent.isWebcomPushNotification(msg.data)) {
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 PushNotificationEvent.Value.Changed -> println("Value has changed: ${event.value}")
is PushNotificationEvent.Value.ChangedHidingData -> println("Value has changed, but you have to read it explicitly")
is PushNotificationEvent.Value.TooBig -> println("Value has changed, but it oversizes the notification")
is PushNotificationEvent.Children.Updated -> {
// get at once update of added/changed/removed nodes
val addedNodes = event.added
val changedNodes = event.changed
val removedNodes = event.removed
println("Some child nodes have changed: ${addedNodes.size} added, ${changedNodes.size} changed, ${removedNodes.size} removed")
// for example, updated node is a list of contacts by ids
when (event.path) {
"/myPath/myUserList" -> {
for ((id, data) in addedNodes) {
val contact = data?.asA(MyContactClass::class)
println("added user id: $id, added: $contact")
}
for ((id, data) in changedNodes) {
val contact = data?.asA(MyContactClass::class)
println("changed user id: $id, update: $contact")
}
for (id in removedNodes) {
println("removed contact: $id")
}
}
else -> {
// if you are watching multiple nodes, you can identify them with their paths. Then you know how to
// cast the data etc...
}
}
}
is PushNotificationEvent.Children.TooBig -> println("Some child nodes have changed, but they oversize the notification")
}
} else {
println("A notification from another provider has been received")
}
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).
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.