Data Exchange and Sync (part 5)

Since the beginning of our journey, we have tried to create a wearable experience starting from our existing handheld app. A first working solution has been achieved just taking advantage of the WearableExtender that has allowed us to easily extend the status bar notifications, providing detailed info and extended functionalities to the wearable device. Even if this approach can work for the majority of the applications and it is really straightforward to achieve, it has some drawbacks in terms of achievable features and availability of content on the smartwatch. In fact, if the smartphone and the wearable device are not coupled, the latter one cannot offer any more access to the previously shown content. Keeping in mind that the wearable device is not a substitute of the handheld one but more like an extension that should try to simplify the user life, we have then exposed a limited number of functionalities into a custom wearable app. In order to achieve this result both applications currently share a common module but are decoupled in terms of single application modules. In this way it is possible to recycle and share code elements like the ContentProvider, the models and common utilities but at the same time keep distinct the different platform based implementations. In Pimp my Wear, this solution has led us to a wearable application that can be used by the user even if the two devices are temporarily disconnected but at the same time to a situation where the content gets out-synced. In this chapter we are going to have a look at how the Wearable Api can help us exchanging and syncing data between the two different devices.

If we have a look at the wearable application developed in the previous chapter, the operations those led us into an inconsistent state between the two devices are the ones those directly affect the underlying data: ‘Purchase’, ‘Add note’ and the bulk operations in the Settings. On the other hand, the handheld device can also update its local data and currently those updates are not directly reflected by the wearable device. Hence, we do not have a unique source of true for the content state and this makes our synchronization even harder to implement. Assuming that we are not trying to create a system that aims to solve all the possible conflicts in the updates but something that keeps the devices synced, we can choose a sync strategy that keeps the most recent update on a book entry, discarding all the ones between the previous state and the new one. In this way we can focus on how we can perform the communication and on the other problems those can rise when interacting between the handheld device and the wearable one.
Another aspect to keep in mind is that both devices are not always connected and because of this we cannot just push any update to the opposite side as soon as it happens. Instead, it has to be possibly persisted and then notified to the other side when a connection is established. We might also want to avoid multiple overlapping updates for the same elements but this can be a further optimization that can save precious resources from being consumed and expensive calls from being performed.

Luckily for us,  these problems are made easier to approach directly by the Google Play Services. Starting from version 5.0, they provide a way to establish a communication channel between the handheld device and the wearable one, that can be accessed through what it is called Wearable Data Layer API.

This API consist on a set of sub-API(s) and elements those can be suitable for different type of scenarios and purposes:

  •  NodeApi: allows to get informations about the nodes those compose the Android Wear network. Through this API, it is possible to distinguish between the local node(the one that refers to the current device) and the connected nodes to it.
  • MessageApi: light-weight API that is meant to be used to send messages with small payloads(not larger than 100kb). The messages can be only shipped to selected nodes those are currently connected and even if they are queued successfully, there is no feedback on receiving by the other node. This can be achieved by the receiver sending back an ACK message to the caller through the same mechanism. The MessageApi is a good candidate for RPC where one device might want to defer heavy computations or commands to another one.
  • DataApi: allows to sync data items and assets between all the nodes in the Android Wear network. If the elements cannot be delivered, they are persisted and then synced later as soon as a connection will be eventually established. Each data item is identified by a unique Uri defined by the source node (the host) and a custom path. If we have got two equal data items those share the same identifier, sending both of them will then result in just one update to all the nodes. This is done to avoid excessive resource consumption and it means that the content of the DataItem needs to change every time if we expect as many updates as many sent items.

 Looking at the synchronization that we want to achieve, the DataApi seems to be a good candidate. It guarantees that every book operation we perform will be synced with every node in the Wearable network, persisting and delaying the sync if necessary. For this scenario, the MessageApi is not the best option because it is meant to work with small payloads and it does not guarantee the delivery of a message to all the nodes. Later, we might want to take advantage of this option in the case we want to allow the handheld device updating its current screen to reflect what the user is currently doing on his smartwatch. This can be seen as kind of remote control mechanism where the user wants to see something in a different way or just bigger on the other device.

In order to get access to the above API(s), the first thing we need to do is including the Wearable API dependency into the build.gradle file:

compile 'com.google.android.gms:play-services-wearable:6.5.87'

After having synced the project with the Gradle files, we can then access all the wearable features, keeping in mind that we have to check if the expected version of the Google Play Services is available on the current device.

if (GooglePlayServicesUtil.isGooglePlayServicesAvailable(this) 
        == ConnectionResult.SUCCESS) { 
    mGoogleApiClient = new GoogleApiClient.Builder(this)
        .addApi(Wearable.API).build(); 
}

The successful creation of the GoogleApiClient for the WearableAPI is not enough but it requires the client to be connected before starting to send messages and data items through the Android Wear network. In the Books application, every time an update to the underlying data happens, we want to make the Wearable Layer aware of the change. Because of that, the ContentValues are then serialized and sent across with a DataItem.

In the Event.DataApi.Builder a PutDataRequest is created:

PutDataMapRequest dataMapRequest = PutDataMapRequest.create(mUri.getPath()); 
DataMap map = dataMapRequest.getDataMap(); 
… 
if (mValues.containsKey(BookDB.Book.OWNED)) { 
    map.putInt(BookDB.Book.OWNED, mValues.getAsInteger(BookDB.Book.OWNED)); 
}
… 
return dataMapRequest.asPutDataRequest();

The data request can now be sent through the DataApi with an already connected client:

Wearable.DataApi.putDataItem(mGoogleApiClient, putDataRequest);

As soon as the DataItem is added to the data layer, it is shipped to all the connected nodes but at the same time it gets tracked for the ones those are not. If the sender is listening for wearable updates, it will then receive the update even if it is already aware of the change. This is an aspect that has to be considered and kept in mind in order to avoid unexpected results and redundant updates.
Each Wearable API offers different listeners to receive the updates from the wearable network. A subscriber can then register through a valid GoogleApiClient instance the listeners for the events that it is interested in and then get updated when a node joins/leaves the network and when a message or a DataItem is sent through.

The wearable package offers also what it is called WearableListenerService, an abstract Service that allows the notifications to get handled in the background without any UI active. Each application can specify at most one single instance of this service and its lifecycle will be handled by Android Wear.

In BooksSyncService.java:

public class BooksSyncService extends WearableListenerService {

    public void onDataChanged(DataEventBuffer dataEvents) {
        List<DataEvent> events = FreezableUtils.freezeIterable(dataEvents); 
        // Running through all the events
        for (DataEvent event : events) {
            if (event.getType() == DataEvent.TYPE_CHANGED) {
                // Checking if it's the same node that has fired the event
                String node = event.getDataItem().getUri().getHost();
                String localNode = WearableUtil.getLocalNode(this).getId();
                if (node.equals(localNode)) {
                    Log.d(TAG_LOG, "Skipping Event - same receiver");
                    continue;
                }
                …
            }
        }
    }

    public void onMessageReceived(MessageEvent messageEvent) { … }

    public void onPeerConnected(Node peer) { … }

    public void onPeerDisconnected(Node peer) { … }
}

The just created service re-uses and accesses internally the ContentProvider that we previously created and this allows any active Cursor on the data to get notified instantly, without any further notification mechanism.

In the mobile and wearable manifests, the WearableListenerService specialization has to be declared in order to allow the binding of the Google Play Services when an update on the Wearable network is available.

<service android:name="com.alchemiasoft.common.sync.BooksSyncService">
    <intent-filter>
        <action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
    </intent-filter>
</service>

The last feature that we want to explore of the Wearable API are messages. As we said before, they can be useful in scenarios where one device wants to defer a computation or just execute a command in an environment that has got the necessary capabilities. One example of that might be a music/video player that can be controlled remotely from the wearable device in order to start/stop/play one media file onto the handheld one.

In the Books application, every time the first page of the GridViewPager is hit, the wearable device sends a message to the first connected node in the wearable network supplying the book id and action to perform (open book).

Wearable.MessageApi.sendMessage(client, targetNode, path, payload)
   .setResultCallback(callback);

On the other side, the handheld application is only interested in those updates only if it is in foreground. Because of this, the WearableListenerService does not handle any of those messages. Instead, they are listen and managed by a MessageListener attached directly from the  HomeActivity that displays every time the book details in full size:

public void onConnected(Bundle connectionHint) {
    // The Client is connected => we can register for the wearable updates 
    Wearable.MessageApi.addListener(mGoogleApiClient, this); 
} 

public void onMessageReceived(MessageEvent messageEvent) {
    Event.MessageApi.Receiver r = Event.MessageApi.Receiver.from(messageEvent); 
    switch (r.action()) {
        case Event.MessageApi.OPEN:
        attachBookFragment(r.bookId());
        break;
    }
}

A Real World Example: Google Fit

If you have ever had a chance to try it out, Google Fit is a concrete example of an application that is syncing data between the two devices. The collected elements are then used to create graphs those summarize the user’s average steps, the activity types and the data that cannot be easily acquired by just the handheld device like the heart rate. For this reason, a wearable device can work not just as an expansion for a smartphone or a tablet, but even as an entry point for new data sources, therefore new opportunities.

 

This all for this time.
..we are almost there at the end!

If you have appreciated this post, I invite you to share it please 🙂

Simone

Advertisements

About alchemiasoft

Android Developer at Bloomberg LP. Algorithms and performance passionate. Curious. Eager to learn.

Posted on February 26, 2015, in Thoughts and tagged , , , , , , , , , , , , , , , , , , , , . Bookmark the permalink. 5 Comments.

  1. Nice tutorial ^_^

    I have a question: Wearable.DataApi.putDataItem(mGoogleApiClient, putDataRequest);
    How do i know if the data is successfully transfered?
    If i check for that it always tells me it was successfull even if my receiver dont get it (by walking away for example).

    • Hi Mathias, thanks! 🙂
      It doesn’t always says success but only if the message is queued. If you want to ensure that the message is received, on the other side you have to send a message back (using maybe the lightweight MessageApi) as an ACK.

  1. Pingback: 1p – Android Wear: Data Exchange and Sync (part 5) | blog.offeryour.com

  2. Pingback: 1 – Android Wear: Data Exchange and Sync (part 5) | WhatCall.com

  3. Pingback: Introduction to Wearables (part 1) | Alchemiasoft

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: