Current limitations
When subscribing to some events on a given data node, the main limitation affects the Child Acknowledgement event, which currently cannot be raised to acknowledge the removal of a child node when this removal results from setting the parent node with a value that involves no child.
Assume we have the {a:"foo"}
value on the watched node. If we set this node with "foo"
, {}
or null
(these
values are “leaf” nodes with no child), the watched node will receive a Child Removal event for child
a
in the not (yet) acknowledged state, but no subsequent Child Acknowledgement event once acknowledged by the
back end.
However, if we had set {b:"bar"}
instead (this value is a “tree” node with one child), then the watched
node would have received in addition a Child Acknowledgement event for child a
once acknowledged by the back end.
Common mistakes
Let's go on with our address book example, and assume it is a very simple application that allows to only add new contacts. Now we want to display a visual feedback when a newly added contact has been taken into account by the back end (ie acknowledged) and so is available for other users of the app. The first attempt often look like:
// 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");
// Update a GUI in real-time each time a new contact is added
contactsNode.subscribe(
Webcom.Event.Child.Addition,
Webcom.Callback(dataSnapshot => {
if (dataSnapshot.event.isChildAddition) {
myView.addItem({name: dataSnapshot.node.key, value: dataSnapshot.val()});
} else { /* dataSnapshot.event.isAck === true */
myView.markItem({name: dataSnapshot.node.key, acknowledged: dataSnapshot.acked});
}
}).includeAcknowledgements() // ask also notifications on acknowledgements
);
import com.orange.webcom.sdk.datasync.subscription.SubscribableEvent.*
val app = WebcomApplication.default
val manager = app.datasyncService.createManager()
val contactsNode = manager / "contacts"
val options = SubscriptionOptions(includesAcknowledgements = true)
// Update a GUI in real-time each time a new contact is added
val subscription = contactsNode.subscribe(Child.Added::class, options = options) { // it: Notification<Child.Added>
if (it is Notification.DataNotification) { // it.data: Child.Added
// the received event (it.data) is either Child.Added or Child.Acknowledged
if (!it.data.isAcknowledgement) // the item update is limited to non-ack events
myView.addItem(name = it.data.node.key, value = it.data.value)
else // the ack update is limited to ack events
myView.markItem(name = it.data.node.key, acknowledged = it.data.value.acknowledged)
} // else { /* revocation and cancelation are ignored */ }
}
In Kotlin, the
Child.Acknowledged
interface inherits allChild.Added
,Child.Changed
andChild.Removed
ones. Consequently, you have to check an event isChild.Acknowledged
BEFORE performing any pattern matching against one of these interfaces.
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: .child(addition: true, acknowledgement: true)) { event in
switch event.eventType {
case .childAddition:
myView.addItem(name: event.key, value: event.value.decoded())
case .childAcknowledgement:
myView.markItem(name: event.key, isAcknowledged: true)
default:
break // other events are not subscribed
}
}
1st problem:
Imagine another user adds a new contact from another device, then our device receives a “Child Addition”
event, which will display a new item (addItem
function) in the not acknowledged state. And then... nothing more:
the added item never switches to the acknowledged state!
Actually, Webcom doesn't necessarily raise a “Child Acknowledgement” event when a new child is
acknowledged: when the added child is already acknowledged by the back end (it is the case when the addition results
from another client), only a “Child Addition” event is raised with the acknowledged
property already
set to true
.
// 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");
// Update a GUI in real-time each time a new contact is added
contactsNode.subscribe(
Webcom.Event.Child.Addition,
Webcom.Callback(dataSnapshot => {
if (dataSnapshot.event.isChildAddition) {
myView.addItem({name: dataSnapshot.node.key, value: dataSnapshot.val()});
} else { /* dataSnapshot.event.isAck === true */
// child acknowledgement does nothing specific
}
// the ack update is common to ALL watched child events
myView.markItem({name: dataSnapshot.node.key, acknowledged: dataSnapshot.acked});
}).includeAcknowledgements() // ask also notifications on acknowledgements
);
import com.orange.webcom.sdk.datasync.subscription.SubscribableEvent.*
val app = WebcomApplication.default
val manager = app.datasyncService.createManager()
val contactsNode = manager / "contacts"
val options = SubscriptionOptions(includesAcknowledgements = true)
// Update a GUI in real-time each time a new contact is added
val subscription = contactsNode.subscribe(Child.Added::class, options = options) { // it: Notification<Child.Added>
if (it is Notification.DataNotification) { // it.data: Child.Added
// the received event (it.data) is either Child.Added or Child.Acknowledged
if (!it.data.isAcknowledgement) // the item update is limited to non-ack events
myView.addItem(name = it.data.node.key, value = it.data.value)
// the ack update is common to ALL watched child events
myView.markItem(name = it.data.node.key, acknowledged = it.data.value.acknowledged)
} // else { /* revocation and cancelation are ignored */ }
}
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: .child(addition: true, acknowledgement: true)) { event in
switch event.eventType {
case .childAddition:
myView.addItem(name: event.key, value: event.value.decoded())
case .childAcknowledgement:
break // .childAcknowledgement does nothing specific
default:
break // other events are not subscribed
}
// the ack update is common to ALL watched child events
myView.markItem(name: event.key, isAcknowledged: event.value.isAcknowledged)
}
2nd problem:
Assume that we include a “date” field in each added child node (that represents a new contact in our address book) to record the date when the contact is added, and that we set this field using the “timestamp” server value (see Synchronize time).
Now I add a new contact from my local client, it appears on the GUI, but bother... again, it doesn't switch to the acknowledged state!
To understand this behavior, when the child node is added, the first raised event is “Child Addition” in the not acknowledged state, with the locally estimated timestamp in the “date” field. Then, when the back end sets the actual timestamp, which is always slightly different from the estimated one, it updates this newly added child, which results in raising a “Child Change” event, this time in the acknowledged state, with the final timestamp value. Consequently, no “Child Acknowledgement” event is raised in this case. Hence, if we forget to watch the “Child Change” event, we miss the acknowledgement of this child node...
// 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");
// Update a GUI in real-time each time a new contact is added
contactsNode.subscribe(
Webcom.Event.Child.Addition.Change,
Webcom.Callback(dataSnapshot => {
if (dataSnapshot.event.isChildAddition) {
myView.addItem({name: dataSnapshot.node.key, value: dataSnapshot.val()});
} else if (dataSnapshot.event.isChildChange) {
myView.updateItem({name: dataSnapshot.node.key, value: dataSnapshot.val()});
} else { /* dataSnapshot.event.isAck === true */
// child acknowledgement does nothing specific
}
// the ack update is common to ALL watched child events
myView.markItem({name: dataSnapshot.node.key, acknowledged: dataSnapshot.acked});
}).includeAcknowledgements() // ask also notifications on acknowledgements
);
import com.orange.webcom.sdk.datasync.subscription.SubscribableEvent.*
val subscription = contactsNode.subscribe(Child.AddedChanged::class, options = options) { // it: Notification<Child.AddedChanged>
if (it is Notification.DataNotification) { // it.data: Child.AddedChanged
// the received event (it.data) is either Child.Added, Child.Changed or Child.Acknowledged
if (!it.data.isAcknowledgement) // the item update is limited to non-ack events
myView.addItem(name = it.data.node.key, value = it.data.value)
// the ack update is common to ALL watched child events
myView.markItem(name = it.data.node.key, acknowledged = it.data.value.acknowledged)
} // else { /* revocation and cancelation are ignored */ }
}
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: .child(addition: true, change: true, acknowledgement: true)) { 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 .childAcknowledgement:
break // .childAcknowledgement does nothing specific
default:
break // other events are not subscribed
}
// the ack update is common to ALL watched child events
myView.markItem(name: event.key, isAcknowledged: event.value.isAcknowledged)
}
3rd problem:
Assume that we add some security rules to allow the addition of new contacts only when the user is authenticated.
Now I add a new contact without having authenticated myself. Surprise: the new contact appears on my GUI (admittedly as not acknowledged) even if the back end prevents its addition!
The explanation is similar to that of the 2nd problem above: when the child node is added it first raises a “Child Addition” event locally on the client, and when the back end forbids this addition, a “Child Removal” event is raised back to the client. So, if we don't watch this child event, we miss the fact that the back end has invalidated the new child node.
// 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");
// Update a GUI in real-time each time a new contact is added
contactsNode.subscribe(
Webcom.Event.Child.Addition.Change.Removal,
Webcom.Callback(dataSnapshot => {
if (dataSnapshot.event.isChildAddition) {
myView.addItem({name: dataSnapshot.node.key, value: dataSnapshot.val()});
} 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});
return; // no need to fallback to mark the acknowledgement status
} else { /* dataSnapshot.event.isAck === true */
// child acknowledgement does nothing specific
}
// the ack update is common to ALL watched child events
myView.markItem({name: dataSnapshot.node.key, acknowledged: dataSnapshot.acked});
}).includeAcknowledgements() // ask also notifications on acknowledgements
);
import com.orange.webcom.sdk.datasync.subscription.SubscribableEvent.*
val app = WebcomApplication.default
val manager = app.datasyncService.createManager()
val contactsNode = manager / "contacts"
val options = SubscriptionOptions(includesAcknowledgements = true)
// Update a GUI in real-time each time a new contact is added
val subscription = contactsNode.subscribe(Child.AddedChangedRemoved::class, options = options) { // it: Notification<Child.AddedChangedRemoved>
if (it is Notification.DataNotification) { // it.data: Child.AddedChangedRemoved
// the received event (it.data) is either Child.Added, Child.Changed, Child.Removed or Child.Acknowledged
if (!it.data.isAcknowledgement) { // the item update is limited to non-ack events
when (it) {
is Child.AddedChanged -> myView.addItem(name = it.data.node.key, value = it.data.value)
is Child.Removed -> {
myView.deleteItem(name = it.data.node.key)
return@subscribe
}
}
}
// the ack update is common to ALL watched child events
myView.markItem(name = it.data.node.key, acknowledged = it.data.value.acknowledged)
} // else { /* revocation and cancelation are ignored */ }
}
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: .child(addition: true, change: true, acknowledgement: true)) { 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)
return // no need to fallback to mark the acknowledgement status
case .childAcknowledgement:
break // .childAcknowledgement does nothing specific
default:
break // other events are not subscribed
}
// the ack update is common to ALL watched child events
myView.markItem(name: event.key, isAcknowledged: event.value.isAcknowledged)
}
As a conclusion, always keep this advice in mind:
It is very often necessary to watch ALL child events, otherwise you are likely to miss unsolicited and unexpected changes on your data.