Data Flow
Introduction
Syncbase API is designed to encourage writing reactive applications where the app updates its UI as data in Syncbase changes. Since Syncbase is a synchronized store the source of data changes might be local or remote as local Syncbase syncs with other peers.
The Watch method is the recommended way of retrieving existing data and watching for changes. Both local and synced mutations are surfaced in the watch stream. This allows developers to stay agnostic to the source of data changes and use the same code to handle local and synced changes alike.
Local mutations are surfaced in the watch stream within milliseconds, which allows apps to be built with unidirectional data flow. Instead of updating the UI optimistically, UI actions can simply mutate Syncbase data. The watch stream will quickly receive local changes and trigger the necessary UI updates.
Reading and Watching Data
addWatchChangeHandler
on Database
can be used to register a handler that will
be called with both initial existing data and any changes to the data later.
Let's consider a simple Todos application where each collection corresponds to
a Todo list and rows in each collection are the tasks. We can use the Watch method
to maintain an in-memory representation of our data model the UI renders
from. UI actions such as adding new task or deleting one simply do a put
or
delete
on the corresponding collection.
cat - <<EOF >> $FILE
db.addWatchChangeHandler(new Database.WatchChangeHandler() {
@Override
public void onInitialState(Iterator<WatchChange> values) {
// onInitialState is called with all of existing data in Syncbase.
// Although the value type is WatchChange, since this is existing
// data, there will not be any values with ChangeType == DELETE_CHANGE
while (values.hasNext()) {
updateState(values.next());
}
// Trigger UI update
}
@Override
public void onChangeBatch(Iterator<WatchChange> changes) {
// onChangeBatch is called whenever changes are made to the data.
// Changes that are part of the same batch are presented together,
// otherwise changes iterator may only contain a single change.
while (changes.hasNext()) {
updateState(changes.next());
}
// Trigger UI update
}
@Override
public void onError(Throwable t) {
// Handle error
}
});
EOF
Modeling our in-memory state as a map of Todolist-Id to a map of (Task-Id, Task)
cat - <<EOF >> $FILE
HashMap<String, Map<String, Task>> state = new HashMap<String, Map<String, Task>>();
// Update the state based on the changes.
void updateState(WatchChange change) {
try {
String collectionId = change.getCollectionId().encode();
String rowKey = change.getRowKey();
if(change.getChangeType() == WatchChange.ChangeType.PUT) {
if(!state.containsKey(collectionId)) {
state.put(collectionId, new HashMap<String, Task>());
}
Task rowValue = change.getValue(Task.class);
state.get(collectionId).put(rowKey, rowValue);
} else if(change.getChangeType() == WatchChange.ChangeType.DELETE) {
state.get(collectionId).remove(rowKey);
}
} catch (SyncbaseException e) {
Log.e("DataFlowExample", "update state error", e);
}
}
EOF
Tip
db.removeAllWatchChangeHandlers()
can be used in activity's onDestroy
to remove all registered watch handlers.
In most cases, source of a change should be irrelevant to the application.
However watchChange.isFromSync()
can tell you if a change is due to a local
mutation or is synced from a remote Syncbase.
Summary
- Syncbase's Watch method is designed to help build reactive applications.
- Watch surfaces both local and synced data changes.
- We recommend using the Watch method to keep an up-to-date in-memory state that the UI renders from.