A very common use-case in Webcom applications is to receive notifications as soon as some data is updated (either by the running client itself or by any other client). This strength of the Webcom back end relies on the real-time feature of its underlying database.
In practice, the idea is to subscribe to a given type of update event on a given data node (or subscriber node). From then on, the callback function registered at subscription will be called for each matching update event on the data at this node. In addition to the data itself, the notified event may contain useful pieces of information such as the acknowledgement status of the data.
More experimented developers may go further and read also Subscribable events in depth.
Subscribable event types
Webcom currently provides 6 types of update events you can subscribe to, split into 2 groups:
Type of update event | Description |
---|---|
Value Change | The value of the subscriber node has been updated. The raised event includes the new value and its acknowledgement status. |
Value Acknowledgement | The value of the subscriber node has been acknowledged by the back end. The raised event includes the value of the node. |
Child Addition |
A new child has been added to the subscriber node. The raised event includes the key of the new child, its value, the key of the preceding child (following the Webcom key ordering), and the acknowledgement status of this child addition. |
Child Change | The value of an existing child of the subscriber node has been updated. The raised event includes the key of the updated node, its value, the key of the preceding child (following the Webcom key ordering), and the acknowledgement status of this child update. |
Child Removal | A child of the subscriber node has been removed. The raised event includes the key of the removed child, the value of the child before its removal, and the acknowledgement status of this child removal. |
Child Acknowledgement | The value of a child of the subscriber node has been acknowledged by the back end (including child removals). The raised event includes the key of the acknowledged child and its value (or null for removed children). |
Within each group, several types of update events may be subscribed simultaneously. For example, Child Addition and Child Removal may be subscribed at the same time, however Child Addition and Value Change cannot. Once a subscription registered, the associated callback function will be called each time an event of the type that has been subscribed is raised. The details for each event type are given in the following sections.
In addition, the callback function may also be notified of the special revocation event. The revocation event is raised by the Webcom back end as soon as the security rules prevent the subscriber node from being read, or the size of the subscriber node becomes too large. Once raised, the revocation event is passed to the callback function, and the associated subscription is automatically canceled, ie the callback will never be called any longer from then on.
Subscribing and unsubscribing
In JavaScript, subscribing relies on the on(...)
method. The first parameter expects one or more subscribed types of
event, while the second one expects the notification callback. The optional third parameters expects a callback for
revocation events.
The notified events passed to the callback are instances of the DataSnapshot
class: among the methods common to all
subscribed types of events, acknowledged()
indicates whether the data update represented by the event is
acknowledged by the back end or local to the client cache, and the eventType()
method returns the type of the
notified event (it is useful when the callback is subscribed to several types of event at a time) (replace “<your-app>” with your actual application identifier):
const ref = new Webcom("<your-app>");
let node = ref.child("contacts");
node.on(["value", "value_ack"], // subscribed types of update events
datasnapshot => { // the callback to call when an event is raised (except revocations)
if (datasnapshot.eventType() === "value") {
console.log("The data at CONTACTS has changed:", JSON.stringify(datasnapshot.val()));
}
console.log("The data at CONTACTS", datasnapshot.acknowledged() ? "is" : "is not yet", "acknowledged");
},
revocationError => { // optional callback for revocation events
console.log("The subscription on CONTACTS has been revoked!", revocationError.message);
});
Unsubscribing uses the off(...)
method:
node.off(); // unsubscibe all callbacks on the node
node.off(["value", "value_ack"]); // unsubscribe all callbaks subscribed to "value" or "value_ack" on the node
In Kotlin, a subscription is registered using the
subscribe()
method of
DatasyncNode
objects, and is managed by the
DatasyncManager
object that has created this DatasyncNode
instance:
- The first argument of this method represents the subscribed types of event, excluding “Value
Acknowledgement” and “Child Acknowledgement”, which are separately requested using the boolean
property
includesAcknowledgements
of theoptions
parameter. The subscribed types of event are given as their common Kotlin type inheriting theSubscribableEvent
interface:Value.Changed::class
for the “Value Change” event type,Child.Added::class
for the “Child Addition” event type,Child.Changed::class
for the “Child Change” event type, etc... - The last argument of the
subscribe()
method is a callback function, which is called for each raised event, including cancelation and revocation events.
The notifications received by the callback may be either:
- A cancelation or revocation notification (passed as instances of
Notification.ControlNotification
subclasses). In this case, it is the last notification to be received by the callback. - A notification of one of the subscribed events (which are all subclasses of both
Notification.DataNotification
andSubscribableEvent
). Among the properties common to all of these subclasses,isAcknowledgement
indicates whether the event is one of the “Value Acknowledgement” or “Child Acknowledgement” events, andvalue
gives theDatasyncValue
associated to the event (including its acknowledgement status).
import com.orange.webcom.sdkv2.datasync.subscription.SubscribableEvent.*
val app = WebcomApplication.default
val manager = app.datasyncService.createManager()
val node = manager / "contacts"
val options = SubscriptionOptions(includesAcknowledgements = true)
val subscription = node.subscribe(Value.Changed::class, options = options) { // it: Notification<Value.Changed>
when (it) {
is Notification.DataNotification -> { // it.data: Value.Changed
if (!it.data.isAcknowledgement) println("The data at CONTACTS has changed: ${it.data.value}")
println("The data at CONTACTS ${if (it.data.value.acknowledged) "is" else "is not"} acknowledged")
}
is Notification.ControlNotification ->
println("The subscription on CONTACTS has been canceled or revoked! ${it.error?.message}")
}
}
Unsubscribing is done using the cancel()
method of the
Subscription
object returned by the previous
subscribe()
method:
subscription.cancel()
Note that subscriptions are managed by
DatasyncManager
objects so that each manager automatically cancels its registered subscription when released (ie garbage-collected). A typical use-case is to create a dedicated manager for eachActivity
orFragment
of your Android app and bind their life-cycles. Thus, this avoids managing explicitly the cancelation of subscriptions, which is generally a tedious task.
In Swift, a subscription is registered using the
subscribe()
method of DatasyncNode
objects and is managed by the DatasyncManager
object that has created this DatasyncNode
instance.
A subscription is specified by a callback function that will be called each time the corresponding event is raised.
The subscribed type of event is represented by a value of the dedicated
DatasyncEventType
enum.
The subscription callback receives as parameter a DatasyncEvent
value that provides convenient methods to retrieve the kind of the just raised event, the value of the associated
data node, as well as the corresponding DatasyncSubscription
object (which makes it possible to individually revoke the subscription without waiting for the manager to do it).
let manager = Webcom.defaultApplication.datasyncService.createManager()
let contactsNode = manager / "contacts"
contactsNode?.subscribe(to: .valueChange) { event in
guard let allContacts: [Contact] = event.value.decoded() else {
return
}
print("The 'contacts' node has \(allContacts.count) child nodes")
}
Subscriptions are managed by
DatasyncManager
objects so that each manager automatically cancels its registered subscription when released. The idea is to create a manager for each view controller and bind their life-cycles, in order to avoid explicitly managing the cancelation of subscriptions, which is generally a tedious task.
Value events
Value events are raised as soon as the value of the subscriber node, considered as a whole, changes. Considering the
value of a nonexistent data node is null
, these events are also raised when the subscriber node is deleted (ie its
value becomes null
).
“Value Change” event
This event is raised:
- Initially at subscription registration, with the current known value of the subscriber node,
- Then each time the value of the subscriber node changes, with its new value.
This event also conveys whether the value of the subscriber node has been acknowledged by the Webcom back end or only updated locally within the client cache.
In JavaScript, the “Value Change” event type to subscribe to is represented by the "value"
string.
The val()
method of the DataSnapshot
object passed to the callback gets the subscriber node value.
const ref = new Webcom("<your-app>");
let contactsNode = ref.child("contacts");
// Display the count of contacts in real time
contactsNode.on("value",
snapshot => console.log("There are now", Object.keys(snapshot.val()).length, "contacts"),
revocationError => console.log("Contact watching revoked:", revocationError.message)
);
In Kotlin, there are 2 variants of the “Value Change” event type:
Value.Changed
, which conveys the value of the subscriber node when raised,Value.ChangedHidingData
, which doesn't convey the value of the subscriber node when raised.
import com.orange.webcom.sdkv2.datasync.subscription.SubscribableEvent.*
val app = WebcomApplication.default
val manager = app.datasyncService.createManager()
val contactsNode = manager / "contacts"
// Display the count of contacts in real time
val subscription = contactsNode.subscribe(Value.Changed::class) { // it: Notification<Value.Changed>
when (it) {
is Notification.DataNotification -> // it.data: Value.Changed
println("There are now ${it.data.value.asMap.size} contacts")
is Notification.ControlNotification -> println("Contact watching revoked: ${it.error?.message}")
}
}
let app = Webcom.defaultApplication
let manager = app.datasyncService.createManager()
let contactsNode = manager / "contacts"
// Display the count of contacts in real time
contactsNode?.subscribe(to: .valueChange) { event in
print("There are now \(event.value.decoded(as: [String: Contact].self)?.count ?? 0) contacts")
} onCompletion: { completion in
if case let .revocation(reason: reason) = completion {
print("Contact watching revoked: \(reason)")
}
}
“Value Acknowledgement” event
This event:
- Is never raised initially at subscription registration,
- Is raised following a “Value Change” event, whose conveyed value of the subscriber node is NOT
acknowledged by the Webcom back end:
- as soon as the value of the subscriber node is acknowledged by the back end,
- AND its value is the same as the one conveyed by the previous “Value Change” event (otherwise a new “Value Change” event is raised).
For convenience, it conveys the acknowledged value of the subscriber node, which is necessarily equal to the value of the previous “Value Change” event, and the acknowledgement status of the value, which is necessarily true.
In JavaScript, the “Value Acknowledgement” event type to subscribe to is represented by the "value_ack"
string.
const ref = new Webcom("<your-app>");
let contactsNode = ref.child("contacts");
// Display a message each time the contact list is acknowledged by the back end
contactsNode.on(["value", "value_ack"], snapshot => {
if (snapshot.acknowledged()) { console.log("The contact list is acknowledged"); }
});
As explained above, in Kotlin, you don't subscribe explicitly to the “Value Acknowledgement” event type.
Instead, you must subscribe to the Value.Changed
(or Value.ChangedHidingData
) event and pass
SubscriptionOptions
with the
includesAcknowledgement
property set to true
to the subscribe()
method.
import com.orange.webcom.sdkv2.datasync.subscription.SubscribableEvent.*
val app = WebcomApplication.default
val manager = app.datasyncService.createManager()
val contactsNode = manager / "contacts"
// Display a message each time the contact list is acknowledged by the back end
val options = SubscriptionOptions(includesAcknowledgements = true)
contactsNode.subscribe(Value.Changed::class, options = options) { // it: Notification<Value.Changed>
if (it is Notification.DataNotification && it.data.value.acknowledged) println("The contact list is acknowledged")
}
As a consequence, contrary to the JavaScript SDK, it is not possible to subscribe to the “Value Acknowledgement” event alone, without subscribing to the “Value Change” event. It is not really a limitation, since this use case is quite useless (see the tip corner below).
let app = Webcom.defaultApplication
let manager = app.datasyncService.createManager()
let contactsNode = manager / "contacts"
// Display a message each time the contact list is acknowledged by the back end
contactsNode?.subscribe(to: .valueEvent) {
if $0.value.isAcknowledged {
print("The contact list is acknowledged")
}
}
Note that, in practice, in order to watch the acknowledgement of the value of a data node, it is necessary to subscribe to BOTH:
- “Value Change” event, in case the notified value is already acknowledged (typically the value has been updated by another client),
- “Value Acknowledgement” event, in case the value is first updated locally on the client (raising the previous event in not acknowledged state) and then acknowledged by the back end.
Child events
Child events are raised as soon as a child of the subscriber node is updated. When watching children, it is possible to distinguish between additions, changes (ie updates of existing children) and removals of children. Such raised events additionally convey, with respect to previous Value events, the key of the concerned child and sometimes the key of the child that precedes the concerned child along the Webcom key ordering.
“Child Addition” event
This event is raised:
- Initially at subscription registration, one time for each of the current known children of the subscriber node,
- Then each time a child node is added to the subscriber node, with the key and the value of this child.
This event also conveys the key of the child of the subscriber node that directly precedes (along the Webcom key ordering) the added child, as well as a boolean indicating whether the added child has been acknowledged by the Webcom back end or only updated locally within the client cache.
In JavaScript, the “Child Addition” event type to subscribe to is represented by the "child_added"
string. The first argument passed to the callback is a DataSnapshot
object, whose name()
method gives the key of
the added child, and the val()
method gets its value. The second argument passed to the callback is a string
representing the key of the child that precedes the added child or null
if it is the 1st child of the subscriber node.
const ref = new Webcom("<your-app>");
let contactsNode = ref.child("contacts");
// Update a GUI in real-time each time a new contact is added
contactsNode.on("child_added",
(snapshot, leftChild) => myView.addItem({name: snapshot.name(), value: snapshot.val(), after: leftChild}),
revocationError => console.log("Contact watching revoked:", revocationError.message)
);
In Kotlin, the “Child Addition” event type is represented by the
Child.Added
interface:
import com.orange.webcom.sdkv2.datasync.subscription.SubscribableEvent.*
val app = WebcomApplication.default
val manager = app.datasyncService.createManager()
val contactsNode = manager / "contacts"
// Update a GUI in real-time each time a new contact is added
val subscription = contactsNode.subscribe(Child.Added::class) { // it: Notification<Child.Added>
when (it) {
is Notification.DataNotification -> // it.data: Child.Added
myView.addItem(name = it.data.node.key, value = it.data.value.asA(Contact::class))
is Notification.ControlNotification -> println("Contact watching revoked: ${it.error?.message}")
}
}
If needed, the previousKey
property gives the key of the preceding child node or null
if the subscriber node is in
first position.
let app = Webcom.defaultApplication
let manager = app.datasyncService.createManager()
let contactsNode = manager / "contacts"
// Update a GUI in real-time each time a new contact is added
contactsNode?.subscribe(to: .childAddition) { event in
myView.addItem(name: event.key, value: event.value.decoded())
} onCompletion: { completion in
if case let .revocation(reason: reason) = completion {
print("Contact watching revoked: \(reason)")
}
}
“Child Change” event
This event:
- Is never raised initially at subscription registration,
- Is raised each time the value of an existing child node of the subscriber node changes (except when the new value of
the child is
null
, in this case a Child Removal event is raised), with the new value of this child.
This event also conveys the key of the child of the subscriber node that directly precedes (along the Webcom key ordering) the changed child, as well as a boolean indicating whether the new value of the child has been acknowledged by the Webcom back end or only updated locally within the client cache.
In JavaScript, the “Child Change” event type to subscribe to is represented by the "child_changed"
string. The first argument passed to the callback is a DataSnapshot
object, whose name()
method gives the key of
the changed child, and the val()
method gets its new value. The second argument passed to the callback is a string
representing the key of the child that precedes the changed child or null
if it is the 1st child of the subscriber
node.
const ref = new Webcom("<your-app>");
let contactsNode = ref.child("contacts");
// Update a GUI in real-time each time a contact is updated
contactsNode.on("child_changed",
(snapshot, leftChild) => myView.updateItem({name: snapshot.name(), value: snapshot.val(), after: leftChild}),
revocationError => console.log("Contact watching revoked:", revocationError.message)
);
In Kotlin, the “Child Change” event type is represented by the
Child.Changed
interface:
import com.orange.webcom.sdkv2.datasync.subscription.SubscribableEvent.*
val app = WebcomApplication.default
val manager = app.datasyncService.createManager()
val contactsNode = manager / "contacts"
// Update a GUI in real-time each time a new contact is added
val subscription = contactsNode.subscribe(Child.Changed::class) { // it: Notification<Child.Changed>
when (it) {
is Notification.DataNotification -> // it.data: Child.Changed
myView.updateItem(name = it.data.node.key, value = it.data.value.asA(Contact::class), after = it.data.previousKey)
is Notification.ControlNotification -> println("Contact watching revoked: ${it.error?.message}")
}
}
As with Child.Added
, the previousKey
property gives the key of the preceding child node or null
if the
subscriber node is in first position.
let app = Webcom.defaultApplication
let manager = app.datasyncService.createManager()
let contactsNode = manager / "contacts"
// Update a GUI in real-time each time a new contact is added
contactsNode?.subscribe(to: .childChange) { event in
myView.updateItem(name: event.key, value: event.value.decoded())
} onCompletion: { completion in
if case let .revocation(reason: reason) = completion {
print("Contact watching revoked: \(reason)")
}
}
“Child Removal” event
This event:
- Is never raised initially at subscription registration,
- Is raised each time an existing child node of the subscriber node is removed (or its value is updated to
null
), with the old value of this child.
This event also conveys whether the removal of the child has been acknowledged by the Webcom back end or only updated locally within the client cache.
In JavaScript, the “Child Removal” event type to subscribe to is represented by the "child_removed"
string. The argument passed to the callback is a DataSnapshot
object, whose name()
method gives the key of
the changed child, and the val()
method gets its deleted value.
const ref = new Webcom("<your-app>");
let contactsNode = ref.child("contacts");
// Update a GUI in real-time each time a contact is deleted
contactsNode.on("child_removed",
snapshot => myView.deleteItem({name: snapshot.name()}),
revocationError => console.log("Contact watching revoked:", revocationError.message)
);
In Kotlin, the “Child Removal” event type is represented by the
Child.Removed
interface:
import com.orange.webcom.sdkv2.datasync.subscription.SubscribableEvent.*
val app = WebcomApplication.default
val manager = app.datasyncService.createManager()
val contactsNode = manager / "contacts"
// Update a GUI in real-time each time a new contact is added
val subscription = contactsNode.subscribe(Child.Removed::class) { // it: Notification<Child.Removed>
when (it) {
is Notification.DataNotification -> // it.data: Child.Removed
myView.deleteItem(name = it.data.node.key)
is Notification.ControlNotification -> println("Contact watching revoked: ${it.error?.message}")
}
}
let app = Webcom.defaultApplication
let manager = app.datasyncService.createManager()
let contactsNode = manager / "contacts"
// Update a GUI in real-time each time a new contact is added
contactsNode?.subscribe(to: .childRemoval) { event in
myView.deleteItem(name: event.key)
} onCompletion: { completion in
if case let .revocation(reason: reason) = completion {
print("Contact watching revoked: \(reason)")
}
}
“Child Acknowledgement” event
This event:
- Is never raised initially at subscription registration,
- Is necessarily raised following a “Child Addition”, “Child Change” or “Child
Removal” event, whose associated child value is NOT acknowledged by the Webcom back end:
- as soon as the value of the subscriber node child is acknowledged by the back end,
- AND its value is the same as the one conveyed by the previous “Child ...” event (otherwise a new “Child ...” event is raised).
For convenience, it conveys the acknowledged value of the subscriber node child, which is necessarily equal to the
value of the previous “Child ...” event (null
for a removed child), and the acknowledgement status of
the value, which is necessarily true.
WARNING: when subscribed to the “Child Acknowledgement” event, you receive all child acknowledgements, even if it follows a child event that is not subscribed. For example, if you subscribe to “Child Addition” and “Child Acknowledgement” events, you may receive a “Child Acknowledgement” event that doesn't necessarily follow the addition of a child to the subscriber node, you will also receive acknowledgements for changes and removal of child nodes.
In practice, a callback often subscribes to all of the child events. Let's wrap up all together in our contact book example:
In JavaScript, the “Child Acknowledgement” event type to subscribe to is represented by the "child_ack"
string.
const ref = new Webcom("<your-app>");
let contactsNode = ref.child("contacts");
// Update a GUI in real-time each time the list of contacts is updated
contactsNode.on(["child_added", "child_changed", "child_removed", "child_ack"],
(snapshot, leftChild) => {
switch (snapshot.eventType()) {
case "child_added":
myView.addItem({name: snapshot.name(), value: snapshot.val(), after: leftChild});
break;
case "child_changed":
myView.updateItem({name: snapshot.name(), value: snapshot.val()});
break;
case "child_removed":
myView.deleteItem({name: snapshot.name()}); // mark the item removed without deleting it
break;
default: // "child_ack" events do nothing here
break;
}
// darken, lighten or delete the item depending on its acknowledged and removed statuses
myView.ackItem({name: snapshot.name(), acknowledged: snapshot.acknowledged()});
},
revocationError => console.log("Contact watching revoked:", revocationError.message)
);
Like with the “Value Acknowledgement” event type, in Kotlin, you don't subscribe explicitly to the
“Child Acknowledgement” event type. Instead, you must subscribe to one or more child events and pass
SubscriptionOptions
with the
includesAcknowledgement
property set to true
to the subscribe()
method.
import com.orange.webcom.sdkv2.datasync.subscription.SubscribableEvent.*
val app = WebcomApplication.default
val manager = app.datasyncService.createManager()
val contactsNode = manager / "contacts"
// Update a GUI in real-time each time a new contact is added
val options = SubscriptionOptions(includesAcknowledgements = true)
val subscription = contactsNode.subscribe(Child.AddedChangedRemoved::class, options = options) { // it: Notification<Child.AddedChangedRemoved>
when (it) {
is Notification.DataNotification -> with(it.data) { // this: Child.AddedChangedRemoved
// this is an instance of either Child.Added, Child.Changed, Child.Removed or Child.Acknowledged
if (!isAcknowledgement) when (this) { // Child.Acknowledged inherits all Child.* interfaces, so it must be checked first
is Child.Added -> myView.addItem(name = node.key, value = value.asA(Contact::class))
is Child.Changed -> myView.updateItem(name = node.key, value = value.asA(Contact::class), after = previousKey)
is Child.Removed -> myView.deleteItem(name = node.key) // mark the item removed without deleting it
}
// darken, lighten or delete the item depending on its acknowledged and removed statuses
myView.markItem(name = node.key, acknowledged = value.acknowledged)
}
is Notification.ControlNotification -> println("Contact watching revoked: ${it.error?.message}")
}
}
As the
Child.Acknowledged
interface inherits allChild.Added
,Child.Changed
andChild.Removed
ones. Consequently, you have to check whether an event isChild.Acknowledged
BEFORE performing any pattern matching against one of these interfaces.
To do so, simply test theisAcknowledgement
property.
Note also how you can subscribe to several child events: there is one interface available for each possible combination:
Child.AddedChanged
for subscribing to both “Child Addition” and “Child Change” events,Child.AddedRemoved
for both “Child Addition” and “Child Removal”, etc...
let app = Webcom.defaultApplication
let manager = app.datasyncService.createManager()
let contactsNode = manager / "contacts"
// Update a GUI in real-time each time the list of contacts is updated
contactsNode?.subscribe(to: .childEvent) { event in
switch event.eventType {
case .childAddition:
myView.addItem(name: event.key, value: event.value.decoded())
case .childChange:
myView.updateItem(name: event.key, value: event.value.decoded())
case .childRemoval:
myView.deleteItem(name: event.key) // mark the item removed without deleting it
case .childAcknowledgement:
break // childAcknowledgement vents do nothing here
default:
break // other events are not subscribed
}
// darken, lighten or delete the item depending on its acknowledged and removed statuses
myView.markItem(name: event.key, isAcknowledged: event.value.isAcknowledged)
} onCompletion: { completion in
if case let .revocation(reason: reason) = completion {
print("Contact watching revoked: \(reason)")
}
}