Tutorial: Subscribing to changes

Serverless DatabaseData Structure Subscribing to changes

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, a 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.

Note that the Webcom SDK provides additional subscription channels, namely notifications over a webhook (mainly for back end clients) or a mobile push messaging system (for mobile clients only).
More experimented developers may also go further and read also Subscribable events in depth.

Subscribable event types

Webcom currently provides 4 types of update events you can subscribe to:

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.
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 child, 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.

While several types of Child related events can be combined and subscribed simultaneously, Value Change and Child related events cannot. For example: Child Addition and Child Removal may be subscribed at the same time, while 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 acknowledgement event. It is raised in case the node update is acknowledged by the back end without its data having changed themselves. This is useful when you need to check some data have been actually taken into account by the Webcom back end (the notion of "acknowledgement" is described hereafter in this page).
  • the revocation event. It 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. The callback will never be called any longer from then on.
  • the cancelation event. It is raised as soon as the corresponding subscription has been canceled by the client application. Once raised, the cancelation event is passed to the callback function, and of course the callback will never be called any longer from then on.

Subscribing and unsubscribing

In JavaScript, subscribing relies on the subscribe(...) method. The first parameter expects the subscribed event type, while the second one expects a notification callback. An optional third parameter makes it possible to specify a query constraint (see Querying data subsets).

The notified events passed to the callback are instances of the DataSnapshot class: among the methods common to all subscribed types of events, the acked property indicates whether the data update represented by the event is acknowledged by the back end or local to the client cache, and the event property returns the type of the actually raised event (it is useful when the callback is subscribed to several types of event at a time).

By default, revocation events are always notified, but you can explicitly exclude them with by calling excludeRevocations() on the passed event descriptor:

// const app = Webcom.App("<your-app>"); // UNCOMMENT if you haven't yet an instance of your app!
const database = app.serverlessDb;
let node = database.rootNode.relativeNode("contacts");

let eventDescriptor = Webcom.Event.Child.Addition.Removal // subscribe to both child added and child removed events
        .excludeRevocations()    // explicitly ask NOT to be notified of revocation events

node.subscribe(
    eventDescriptor, 
    Webcom.Callback(dataSnapshot => { // the callback to call when an event is raised (except revocation events)
        if (dataSnapshot.event.isChildAddition) {
            console.log(`The contact ${dataSnapshot.node.key} is added, with value ${JSON.stringify(dataSnapshot.val())}`);
            console.log("The data ", dataSnapshot.acked ? "are" : "are not yet", "acknowledged")
        } else {
            console.log(`The contact ${dataSnapshot.node.key} is removed`);
        }
    })
);

The Callback objects may be customized using:

  • the onCanceled() method to specify a dedicated callback to be called either when the subscription is revoked by the Webcom back end or explicitly canceled by the client application.
  • the includeAcknowledgements() method to call the callback function also when the subscribed data is acknowledged by the Webcom back end without change.
// const app = Webcom.App("<your-app>"); // UNCOMMENT if you haven't yet an instance of your app!
const database = app.serverlessDb;
let node = database.rootNode.relativeNode("contacts");

// build a callback
const callback = Webcom.Callback(dataSnapshot => { // the callback to call when an event is raised
    if (dataSnapshot.event.isChildAddition) {
        console.log(`The contact ${dataSnapshot.node.key} is added, with value ${JSON.stringify(dataSnapshot.val())}`);
        console.log("The data ", dataSnapshot.acked ? "are" : "are not yet", "acknowledged")
    } else {
        console.log(`The contact ${dataSnapshot.node.key} is removed`);
    }
});
callback.includeAcknowledgements();  // notify also acknowledgements
callback.onCanceled(
    (error) => {  // defines a 'revocation' callback
        if (error === null) {console.log("Subscription explicitly canceled by client app");}
        else {console.log("Subscription revoked by Webcom back end, with cause: ", error.message);} 
    }
);

// then subscribe to events
node.subscribe(Webcom.Event.Child.Addition.Removal, callback);

Examples of valid event descriptors:

Webcom.Event.ValueChange
Webcom.Event.ValueChange.WithoutData
Webcom.Event.ValueChange.excludeRevocations()
Webcom.Event.Child.Change.Addition.Removal

Unsubscribing may be done using either the unsubscribe(...) method on a data node, or the cancel() method on a given subscription:

// const app = Webcom.App("<your-app>"); // UNCOMMENT if you haven't yet an instance of your app!
const database = app.serverlessDb;
let node = database.rootNode.relativeNode("contacts");

// first subscribe
let myCallback = Webcom.Callback(/*...*/); // please see code sample above
let mySubscription;
node.subscribe(Webcom.Event.ValueChange, myCallback)  
    .then((subscription) => mySubscription = subscription)  // get the returned subscription when the promise is resolved
// ... do some stuff
// then unsubscribe to one callback (other callbacks may remain for this node)
node.unsubscribe(myCallback) // from the node
// or
mySubscription.cancel()      // another way do do the same, from the received subscription

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 the options parameter. The subscribed types of event are given as their common Kotlin type inheriting the SubscribableEvent 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 and SubscribableEvent). Among the properties common to all of these subclasses, isAcknowledgement indicates whether the event is one of the “Value Acknowledgement” or “Child Acknowledgement” events, and value gives the DatasyncValue associated to the event (including its acknowledgement status).
import com.orange.webcom.sdk.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 each Activity or Fragment 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.

Not available in REST API

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 is represented by the Webcom.Event.ValueChange constant. The val() method of the DataSnapshot object passed to the callback gets the subscriber node value.

// const app = Webcom.App("<your-app>"); // UNCOMMENT if you haven't yet an instance of your app!
const database = app.serverlessDb;
let contactsNode = database.rootNode.relativeNode("contacts");
// Display the count of contacts in real time
contactsNode.subscribe(Webcom.Event.ValueChange,
    Webcom.Callback(dataSnapshot => {
        console.log("There are now", Object.keys(dataSnapshot.val()).length, "contacts");
    })
);

In Kotlin, there are 2 variants of the “Value Change” event type:

import com.orange.webcom.sdk.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

That's not an explicit event type as such but, when subscribing to “Value Change”, you can ask to be also notified of data acknowledgments. In other words, it is not possible to subscribe to the “Value Change 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).

An acknowledgment 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, you ask receiving acknowledgement events by calling the includeAcknowledgements() method of the Callback object passed to the subscribe() method.

// const app = Webcom.App("<your-app>"); // UNCOMMENT if you haven't yet an instance of your app!
const database = app.serverlessDb;
let contactsNode = database.rootNode.relativeNode("contacts");

let callback = Webcom.Callback(/*...*/) // define a callback
        .includeAcknowledgements()  // ask notifications on acknowledgements

// Display a message each time the contact list is acknowledged by the back end
contactsNode.subscribe(
    Webcom.Event.ValueChange,
    Webcom.Callback(dataSnapshot => {
        if (dataSnapshot.event.isAck) { console.log("The contact list has just been acknowledged"); } 
        /* else the contact list has just been updated (in the acknowledged state or not) */
    })
);

In Kotlin, you ask receiving acknowledgement events by passing a SubscriptionOptions object with the includesAcknowledgement property set to true to the subscribe() method.

import com.orange.webcom.sdk.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.isAcknowledgement)
      println("The contact list has just been acknowledged")
  /* else the contact list has just been updated (in the acknowledged state or not) */
}
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 (by asking to receive additionally acknowledgement notifications), 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 is represented by the Webcom.Event.Child.Addition constant.

The first argument passed to the callback is a DataSnapshot object, whose node.key property gives the key of the added child, and whose 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 first child of the subscriber node.

// const app = Webcom.App("<your-app>"); // UNCOMMENT if you haven't yet an instance of your app!
const database = app.serverlessDb;
let node = database.rootNode.relativeNode("contacts");
// Update a GUI in real-time each time a new contact is added
node.subscribe(Webcom.Event.Child.Addition,
    Webcom.Callback((dataSnapshot, previousChildKey)  => {
        myView.addItem({name: dataSnapshot.node.key, value: dataSnapshot.val(), after: previousChildKey});
    })
);

In Kotlin, the “Child Addition” event type is represented by the Child.Added interface:

import com.orange.webcom.sdk.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 is represented by the Webcom.Event.Child.Change constant.

The first argument passed to the callback is a DataSnapshot object, whose node.key property gives the key of the changed child, and whose 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 first child of the subscriber node.

// const app = Webcom.App("<your-app>"); // UNCOMMENT if you haven't yet an instance of your app!
const database = app.serverlessDb;
let node = database.rootNode.relativeNode("contacts");
// Update a GUI in real-time each time a new contact is updated
node.subscribe(Webcom.Event.Child.Change,
    Webcom.Callback((dataSnapshot, previousChildKey) => {
        myView.updateItem({name: dataSnapshot.node.key, value: dataSnapshot.val(), after: previousChildKey});
    })
);

In Kotlin, the “Child Change” event type is represented by the Child.Changed interface:

import com.orange.webcom.sdk.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 updated
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 is represented by the Webcom.Event.Child.Removal constant. The argument passed to the callback is a DataSnapshot object, whose node.key method gives the key of the removed child, and whose val() method gets its deleted value.

// const app = Webcom.App("<your-app>"); // UNCOMMENT if you haven't yet an instance of your app!
const database = app.serverlessDb;
let node = database.rootNode.relativeNode("contacts");
// Update a GUI in real-time each time a new contact is deleted
node.subscribe(Webcom.Event.Child.Removal,
    Webcom.Callback(dataSnapshot => {
        myView.deleteItem({name: dataSnapshot.node.key});
    })
);

In Kotlin, the “Child Removal” event type is represented by the Child.Removed interface:

import com.orange.webcom.sdk.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 deleted
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

Similarly to the “Value Acknowledgement” event, the “Child Acknowledgement” event is not an explicit event type as such but, when subscribing to a child event, you can ask to be also notified of data acknowledgments. In other words, it is not possible to subscribe to the “Child Acknowledgement” event alone, without subscribing to any of the child events. It is not really a limitation, since this use case is quite useless.

An acknowledgment 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 asking to be notified of child data acknowledgements, you receive child acknowledgements related to all child event types (i.e. addition, change or removal) event if it follows a child event that is not subscribed. For example, if you subscribe to “Child Addition” events, including acknowledgements, you will also receive acknowledgements for changes and removals of child nodes.

In practice, a callback often subscribes to all the child events. Let's wrap up all together in our contact book example:

As with “Value Acknowledgement” events, you ask receiving acknowledgements by calling the includeAcknowledgements() method of the Callback object passed to the subscribe() method.
In addition, note how the various child events can be combined:

// const app = Webcom.App("<your-app>"); // UNCOMMENT if you haven't yet an instance of your app!
const database = app.serverlessDb;
let node = database.rootNode.relativeNode("contacts");

// Update a GUI in real-time each time the list of contacts is updated
node.subscribe(
    Webcom.Event.Child.Removal.Addition.Change, // subscribe to all child events at the same time
    Webcom.Callback((dataSnapshot, previousChildKey) => {
        if (dataSnapshot.event.isChildAddition) {
            myView.addItem({name: dataSnapshot.node.key, value: dataSnapshot.val(), after: previousChildKey});
        } else if (dataSnapshot.event.isChildChange) {
            myView.updateItem({name: dataSnapshot.node.key, value: dataSnapshot.val()});
        } else if (dataSnapshot.event.isChildRemoval) {
            myView.deleteItem({name: dataSnapshot.node.key}); // mark the item removed without deleting it
        } else if (dataSnapshot.event.isAck) {
            // child acknowledgement events do nothing here
        }
        // darken, lighten or delete the item depending on its acknowledged and removed statuses
        myView.ackItem({name: dataSnapshot.node.key, acknowledged: dataSnapshot.acked});
    }).includeAcknowledgements() // ask also notifications on acknowledgements
);

As with “Value Acknowledgement” events, you ask receiving acknowledgements by passing a SubscriptionOptions object with the includesAcknowledgement property set to true to the subscribe() method.

import com.orange.webcom.sdk.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}")
  }
}

The Child.Acknowledged interface inherits all Child.Added, Child.Changed and Child.Removed ones. Consequently, you have to check whether an event is Child.Acknowledged BEFORE performing any pattern matching against one of these interfaces.
To do so, simply test the isAcknowledgement 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)")
    }
}