Android Wear:

Accessing the Data Layer API

Android Wear API Layers

Introduction

The Android documentation provides lots of examples for sending messages and storing data with the different API layers. However, it doesn’t provide any examples of how to access the Data Layer API and retrieve information from it.

In this article, we’re going to see what the Data Layer API is, how to store information there and how to retrieve it with an example.

We’ll make use of the different layers: Message API for just sending a simple message to certain node, and Node API to know more information about ourselves and the node we’re connected to.

What’s the Data Layer API?

Data Layer API

According to Android Wear documentation: “The Wearable Data Layer API, which is part of Google Play services, provides a communication channel for your handheld and wearable apps. The API consists of a set of data objects that the system can send and synchronise over the wire and listeners that notify your apps of important events with the data layer”.

The Data Layer API allows us to store and retrieve data from different devices, in this case we’re interested in a data interchange between smartwatch and mobile devices.

Android Wear documentation makes it pretty clear how to store data in this layer and how to listen for it as soon as it’s stored. But, what about if we want to retrieve that information once it’s been stored? Let’s take a look.

Data Layer API structure

Sending/Storing Data and how to listen for those changes

Here’s the easy bit, we have available a lot of examples of how to do this in the Android Wear documentation. If we follow it, we can create a DataItem object and send it with a PutDataMapRequest. We just need a connection between devices and that’s it.

How to connect a device to the GoogleApiClient:

public class MobileWearableService extends WearableListenerService
implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener
{
private GoogleApiClient mGoogleApiClient;
private boolean nodeConnected = false;
@Override
public void onCreate()
{
super.onCreate();
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Wearable.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
mGoogleApiClient.connect();
}
@Override
public void onConnected(Bundle bundle)
{
nodeConnected = true;
}

@Override
public void onConnectionSuspended(int i)
{
nodeConnected = false;
}

@Override
public void onConnectionFailed(ConnectionResult connectionResult)
{
nodeConnected = false;
}

And we can send information from one node to another as follows:

private void sendData(final String dataToSend)
{
new Thread(new Runnable()
{
@Override
public void run()
{
if (!nodeConnected)
{
mGoogleApiClient.blockingConnect(TIMEOUT_S, TimeUnit.SECONDS);
}
if (!nodeConnected)
{
Log.e("WEAR APP", "Failed to connect to mGoogleApiClient within " + TIMEOUT_S + " seconds");
return;
}

if (mGoogleApiClient.isConnected())
{
PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(PATH_DATA);
putDataMapRequest.getDataMap().putString("data", dataToSend);

PutDataRequest request = putDataMapRequest.asPutDataRequest();

PendingResult<DataApi.DataItemResult> pendingResult =
Wearable.DataApi.putDataItem(mGoogleApiClient, request);

pendingResult.setResultCallback(new ResultCallback<DataApi.DataItemResult>()
{
@Override
public void onResult(DataApi.DataItemResult dataItemResult)
{
Log.e("WEAR APP", "APPLICATION Result has come");
}
});

} else {
Log.e("WEAR APP", "No Google API Client connection");
}
}
}).start();
}

This is one of the ways we can send information between nodes. The other node can listen for it by means of a service. The service has also to connect to the GoogleAPIClient and implements the WearableListenerService interface. We listen for changes with the onDataChanged method.

@Override
public void onDataChanged(DataEventBuffer dataEvents)
{
final ArrayList<DataEvent> events = FreezableUtils.freezeIterable(dataEvents);

for (DataEvent event : events)
{
PutDataMapRequest putDataMapRequest =
PutDataMapRequest.createFromDataMapItem(DataMapItem.fromDataItem(event.getDataItem()));

String path = event.getDataItem().getUri().getPath();
if (event.getType() == DataEvent.TYPE_CHANGED)
{
if (PATH_DATA.equals(path))
{
DataMap data = putDataMapRequest.getDataMap();
String data = data.getString("data");
}
else if (event.getType() == DataEvent.TYPE_DELETED)
{

}
}
}
OnDataChanged is only called when the data in certain path has changed. It can be that there were no data and now there’s data available, or that the content has changed.

Problem to solve

Now we know how to send, store and listen for data between nodes, let’s imagine how this would work with an example. What we want to do is:

When the Wear app starts, it’s going to check if we have data stored, in case there’s no data in the path: we’re going to send a message via Message Layer API to our Mobile app (indicating we want that data), the Mobile app is going to receive the message and it’s going to send the data back via Data Layer API (always the same data).

Because we don’t know how to check if we have data stored in certain path, let’s assume that the method in charge of that is always going to return false. Thus, when the Wear app starts, it’s always going to send the message to receive the data via Data Layer API.

First execution

The Wear app starts. No data available. It sends a message to get the data. The Mobile app (which is listening for messages from the Wear app) sends the data back via Data Layer API. The Wear app service listens for the change and it gets the event that the data has changed.

Second execution

The Wear app starts. There’s data available, but because we don’t know how to retrieve it yet, we assume that there’s no data. It sends a message to the mobile device to get the data (as before). The Mobile app listener gets the message and sends the data back via Data Layer API. The Wear app service listens for the change and it DOES NOT get the event that the data has changed.

Why it doesn’t get the notification the second time? We don’t get the event the second time since the data was previously stored and it didn’t change from one execution to another. That’s why is important knowing how to retrieve data from the Data Layer API.

Android Wear documentation doesn’t provide any example of this.

When you send/store information through the Data API Layer, the services are going to be listening for changes, just changes. If you send the same information twice, it’s not going to call onDataChanged.

Accessing and retrieving information from the Data API Layer

Steps we need to follow:

  1. We have to be connected to the GoogleAPIClient (we saw how to do it before, first code snippet).
  2. We need to know where the data is stored. Thus, we need a URI.
  3. Get the information.

The second step is the trickiest one. The URI has to follow this format: “wear://<some guid here>/path_to_data”. We know the path to the data we want to get, we know that the Scheme is wear (the prefix is always the same). What about the ID? We need to know the ID of the node who stored the data previously.

When creating the URI to access the Data Layer API, the node ID corresponds to the node which stored the data.

As you may know, a Wear device can be potentially connected to different Mobile devices (obviously, not at the same time but it can be during its life time). We have to consider this information when we create our own app. Is the information we want to send specific for each device?

Let’s say we want to access to the Data Layer API from our Wear app. In this case, it’s possible we have multiple data in the same Path (because different devices stored it previously). It’s not a problem if different devices store data in the same path because the URI will be different (each device has an unique NodeID).

We have to ask two different questions: Who stored that information? And … Do we want the information of one node (the mobile device we’re currently connected to)? or do we want the information of all the nodes we’ve been connected to?

Let’s see first how to build a URI and we’ll see the different situations.

Building the URI

As we saw before, to build a URI we need the scheme (for Wear app is always going to be the same), the path to the content and the authority.

The important thing here is the authority field. This is the ID of the node which stored the information. We can create an URI object without the authority field, that will retrieve all the data stored from all the different nodes in that path.

Uri uri = new Uri.Builder()
.scheme(PutDataRequest.WEAR_URI_SCHEME)
.path(pathToContent)
.authority(nodeId)
.build();

Situation 1: The node which access to the Data Layer API it’s the one which stored the information

If you want to access to the Data Layer API with the node which stored the information, you need to know the ID of your current node. How to get it?

We use the Node Layer API and call getLocalNode. We wait the resultCallback and then we call the Data Layer API with getDataItem. That means we just want the DataItem which is in that path and was stored by that specific node. We’ll get a DataItemResult from which we can get the information stored.

private void getData(final String pathToContent) {
Wearable.NodeApi.getLocalNode(mGoogleApiClient).setResultCallback(new ResultCallback<NodeApi.GetLocalNodeResult>() {
@Override
public void onResult(NodeApi.GetLocalNodeResult getLocalNodeResult) {

Uri uri = new Uri.Builder()
.scheme(PutDataRequest.WEAR_URI_SCHEME)
.path(pathToContent)
.authority(getLocalNodeResult.getNode().getId())
.build();

Wearable.DataApi.getDataItem(mGoogleApiClient, uri)
.setResultCallback(
new ResultCallback<DataApi.DataItemResult>() {
@Override
public void onResult(DataApi.DataItemResult dataItemResult) {

if (dataItemResult.getStatus().isSuccess() && dataItemResult.getDataItem() != null) {
DataMap data = DataMap.fromByteArray(dataItemResult.getDataItem().getData());
String data = data.getString("data");

}
}
}
);
}

Situation 2: The data was stored by the Node I’m connected to

Now, it was the node we’re connected to the one which stored the information. How do we know which node are we connected to?

We access to the Node Layer API and call GetConnectedNodes. The resultCallback of this request is going to return the different connections established by our node. Because we’re connected to GoogleAPILayer, we’re going to get that connection as well. So for getting the ID of the Mobile device (for example) we just have to get the last one.

private Node connectedNode;
private void getConnectedNode()
{
Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).setResultCallback(new ResultCallback<NodeApi.GetConnectedNodesResult>() {
@Override
public void onResult(NodeApi.GetConnectedNodesResult nodes) {
for (Node node : nodes.getNodes()) {
connectedNode = node;
}
}
});
}

Now we can use the same code than before but updating the URI:

Uri uri = new Uri.Builder()
.scheme(PutDataRequest.WEAR_URI_SCHEME)
.path(pathToContent)
.authority(connectedNode.getId())
.build();

Situation 3: We want all the data stored by different nodes

For this situation, we create the URI without the authority field, and instead of calling getDataItem, we call getDataItems:

private void getData(final String pathToContent) {
Uri uri = new Uri.Builder()
.scheme(PutDataRequest.WEAR_URI_SCHEME)
.path(pathToContent)
.build();


Wearable.DataApi.getDataItems(mGoogleApiClient, uri)
.setResultCallback(new ResultCallback<DataItemBuffer>() {
@Override
public void onResult(DataItemBuffer dataItems) {
for(int i=0;i<dataItems.getCount();i++)
{
Log.d("WEAR APP", "The data is from: " + dataItems.get(i).getUri().getAuthority());
DataMap data = DataMap.fromByteArray(dataItems.get(i).getData());
String data = data.getString("data");
}
}
}
);
}

Conclusion

The problem we had at the beginning of retrieving data from Data Layer API is solved. There are few considerations to take into account before accessing to it.

Remember the different options you have for accessing the Data layer, probably who stored the data question is the most important one. Also, if you store/send the same information more than once, onDataChanged will only be called once (just the first time).

Thanks for reading,

Manuel Vicente Vivo