When you make a call to the Data Layer API, you can receive the status of the call when it completes. You also can listen for data events resulting from data changes that your app makes anywhere on the Wear OS by Google network.
For an example of effectively working with the Data Layer API, check out the Android DataLayer Sample app.
Wait for the status of Data Layer calls
Calls to the Data Layer API—such as a call using the putDataItem
method of the
DataClient
class—sometimes return a
Task<ResultType>
object. As soon as the Task
object
is created, the operation is
queued in the background. If you do nothing more after this, the operation
eventually completes silently.
However, you usually want to do something with
the result after the operation completes, so the Task
object lets
you wait for the result status, either asynchronously or synchronously.
Asynchronous calls
If your code is running on the main UI thread, don't make blocking calls to the
Data Layer API. Run the calls asynchronously by adding a callback method
to the Task
object, which fires when the operation is completed:
Kotlin
// Using Kotlin function references task.addOnSuccessListener(::handleDataItem) task.addOnFailureListener(::handleDataItemError) task.addOnCompleteListener(::handleTaskComplete) ... fun handleDataItem(dataItem: DataItem) { ... } fun handleDataItemError(exception: Exception) { ... } fun handleTaskComplete(task: Task<DataItem>) { ... }
Java
// Using Java 8 Lambdas. task.addOnSuccessListener(dataItem -> handleDataItem(dataItem)); task.addOnFailureListener(exception -> handleDataItemError(exception)); task.addOnCompleteListener(task -> handleTaskComplete(task));
See the Task API for other possibilities, including chaining the execution of different tasks.
Synchronous calls
If your code is running on a separate handler thread in a background service,
such as in a
WearableListenerService
,
it's fine for the calls to block. In this case, you can call Tasks.await()
on the Task
object, which blocks until the request completes and returns a
Result
object. This is shown in the following example.
Note: Make sure not to call this while on the main thread.
Kotlin
try { Tasks.await(dataItemTask).apply { Log.d(TAG, "Data item set: $uri") } } catch (e: ExecutionException) { ... } catch (e: InterruptedException) { ... }
Java
try { DataItem item = Tasks.await(dataItemTask); Log.d(TAG, "Data item set: " + item.getUri()); } catch (ExecutionException | InterruptedException e) { ... }
Listen for Data Layer events
Because the data layer synchronizes and sends data across the handheld and wearable devices, you usually need to listen for important events like data items being created and messages being received.
To listen for data layer events, you have two options:
- Create a service that extends
WearableListenerService
. - Create an activity or class that implements the
DataClient.OnDataChangedListener
interface.
With both of these options, you override the data event callback methods for the events you are interested in handling.
Note: Consider your app's battery usage when choosing
a listener implementation. A WearableListenerService
is registered in the app's manifest and can launch the app if it is not already
running. If you only need to listen for events when your app is already running,
which is often the case with interactive applications, then don't use a
WearableListenerService
. Instead, register a live listener.
For example, use the addListener
method of the DataClient
class. This can reduce the load on the system and reduce battery usage.
Use a WearableListenerService
You typically create instances of
WearableListenerService
in both your wearable and
handheld apps. However, if you are not interested in data events in one of the
apps, then you don't need to implement the service in that app.
For example, you can have a handheld app that sets and gets data item objects and a wearable app that listens for these updates to update its UI. The wearable app never updates any of the data items, so the handheld app doesn't listen for any data events from the wearable app.
Some of the events you can listen for using
WearableListenerService
are the following:
-
onDataChanged()
: whenever a data item object is created, deleted, or changed, the system triggers this callback on all connected nodes. -
onMessageReceived()
: a message sent from a node triggers this callback on the target node. -
onCapabilityChanged()
: when a capability that an instance of your app advertises becomes available on the network, that event triggers this callback. If you're looking for a nearby node, you can query theisNearby()
method of the nodes provided in the callback.
You can also listen for events from
ChannelClient.ChannelCallback
, such as onChannelOpened()
.
All the preceding events are executed in a background thread, not on the main thread.
To create a WearableListenerService
, follow these steps:
- Create a class that extends
WearableListenerService
. - Listen for the events that you're interested in, such as
onDataChanged()
. - Declare an intent filter in your Android manifest to notify the system about your
WearableListenerService
. This declaration lets the system bind your service as needed.
The following example shows how to implement a simple WearableListenerService
:
Kotlin
private const val TAG = "DataLayerSample" private const val START_ACTIVITY_PATH = "/start-activity" private const val DATA_ITEM_RECEIVED_PATH = "/data-item-received" class DataLayerListenerService : WearableListenerService() { override fun onDataChanged(dataEvents: DataEventBuffer) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "onDataChanged: $dataEvents") } // Loop through the events and send a message // to the node that created the data item. dataEvents.map { it.dataItem.uri } .forEach { uri -> // Get the node ID from the host value of the URI. val nodeId: String = uri.host // Set the data of the message to be the bytes of the URI. val payload: ByteArray = uri.toString().toByteArray() // Send the RPC. Wearable.getMessageClient(this) .sendMessage(nodeId, DATA_ITEM_RECEIVED_PATH, payload) } } }
Java
public class DataLayerListenerService extends WearableListenerService { private static final String TAG = "DataLayerSample"; private static final String START_ACTIVITY_PATH = "/start-activity"; private static final String DATA_ITEM_RECEIVED_PATH = "/data-item-received"; @Override public void onDataChanged(DataEventBuffer dataEvents) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "onDataChanged: " + dataEvents); } // Loop through the events and send a message // to the node that created the data item. for (DataEvent event : dataEvents) { Uri uri = event.getDataItem().getUri(); // Get the node ID from the host value of the URI. String nodeId = uri.getHost(); // Set the data of the message to be the bytes of the URI. byte[] payload = uri.toString().getBytes(); // Send the RPC. Wearable.getMessageClient(this).sendMessage( nodeId, DATA_ITEM_RECEIVED_PATH, payload); } } }
The following section explains how to use an intent filter with this listener.
Use filters with WearableListenerService
An intent filter for the WearableListenerService
example shown in the previous section
might look like this:
<service android:name=".DataLayerListenerService" android:exported="true" tools:ignore="ExportedService" > <intent-filter> <action android:name="com.google.android.gms.wearable.DATA_CHANGED" /> <data android:scheme="wear" android:host="*" android:path="/start-activity" /> </intent-filter> </service>
In this filter, the DATA_CHANGED
action replaces the
previously recommended BIND_LISTENER
action so that only specific
events wake or launch your app. This change improves system efficiency
and reduces battery consumption and other overhead associated with your
app. In this example, the watch listens for the
/start-activity
data item, and the
phone listens for the /data-item-received
message response.
Standard Android filter matching rules apply. You can specify multiple services
per manifest, multiple intent filters per service, multiple actions per filter,
and multiple data stanzas per filter. Filters can match on a wildcard host or on
a specific one. To match on a wildcard host, use host="*"
. To match
on a specific host, specify host=<node_id>
.
You can also match a literal path or path prefix. To do this, you must specify a wildcard or specific host. Otherwise, the system ignores the path you specify.
For more information about the filter types that Wear OS supports, see the
API reference documentation for
WearableListenerService
.
For more information on data filters and matching rules, see the API reference
documentation for the <data>
manifest element.
When matching intent filters, remember two important rules:
- If no scheme is specified for the intent filter, the system ignores all the other URI attributes.
- If no host is specified for the filter, the system ignores all the path attributes.
Use a live listener
If your app only cares about data-layer events when the user is interacting with the app, it may not need a long-running service to handle every data change. In such a case, you can listen for events in an activity by implementing one or more of the following interfaces:
DataClient.OnDataChangedListener
MessageClient.OnMessageReceivedListener
CapabilityClient.OnCapabilityChangedListener
ChannelClient.ChannelCallback
To create an activity that listens for data events, do the following:
- Implement the desired interfaces.
- In the
onCreate()
oronResume()
method, callWearable.getDataClient(this).addListener()
,MessageClient.addListener()
,CapabilityClient.addListener()
, orChannelClient.registerChannelCallback()
to notify Google Play services that your activity is interested in listening for data layer events. - In
onStop()
oronPause()
, unregister any listeners withDataClient.removeListener()
,MessageClient.removeListener()
,CapabilityClient.removeListener()
, orChannelClient.unregisterChannelCallback()
. - If an activity is only interested in events with a specific path prefix, you can add a listener with a suitable prefix filter to only receive data that is relevant to the current application state.
- Implement
onDataChanged()
,onMessageReceived()
,onCapabilityChanged()
, or methods fromChannelClient.ChannelCallback
, depending on the interfaces that you implemented. These methods are called on the main thread, or you can specify a customLooper
usingWearableOptions
.
Here's an example that implements DataClient.OnDataChangedListener
:
Kotlin
class MainActivity : Activity(), DataClient.OnDataChangedListener { public override fun onResume() { Wearable.getDataClient(this).addListener(this) } override fun onPause() { Wearable.getDataClient(this).removeListener(this) } override fun onDataChanged(dataEvents: DataEventBuffer) { dataEvents.forEach { event -> if (event.type == DataEvent.TYPE_DELETED) { Log.d(TAG, "DataItem deleted: " + event.dataItem.uri) } else if (event.type == DataEvent.TYPE_CHANGED) { Log.d(TAG, "DataItem changed: " + event.dataItem.uri) } } } }
Java
public class MainActivity extends Activity implements DataClient.OnDataChangedListener { @Override public void onResume() { Wearable.getDataClient(this).addListener(this); } @Override protected void onPause() { Wearable.getDataClient(this).removeListener(this); } @Override public void onDataChanged(DataEventBuffer dataEvents) { for (DataEvent event : dataEvents) { if (event.getType() == DataEvent.TYPE_DELETED) { Log.d(TAG, "DataItem deleted: " + event.getDataItem().getUri()); } else if (event.getType() == DataEvent.TYPE_CHANGED) { Log.d(TAG, "DataItem changed: " + event.getDataItem().getUri()); } } } }
Use filters with live listeners
As previously mentioned, just as you can specify intent filters for
manifest-based WearableListenerService
objects, you can use intent filters when registering a live listener through the
Wearable
API. The same rules apply to both API-based live listeners and
manifest-based listeners.
A common pattern is to register a listener with a specific path or path prefix
in an activity’s onResume()
method, and then to remove the listener in the activity’s
onPause()
method.
Implementing listeners in this fashion lets your app more selectively
receive events, improving its design and efficiency.