This lesson teaches you to
When you make calls to the Data Layer API, you can receive the status of the call when it completes as well as listen for any changes that the call ends up making with listeners.
Wait for the Status of Data Layer Calls
You'll notice that calls to the Data Layer API sometimes return a
PendingResult
,
such as
putDataItem()
.
As soon as the PendingResult
is created,
the operation is queued in the background. If you do nothing else after this, the operation
eventually completes silently. However, you'll usually want to do something with the result
after the operation completes, so the
PendingResult
lets you wait for the result status, either synchronously or asynchronously.
Asynchronous calls
If your code is running on the main UI thread, do not make blocking calls
to the Data Layer API. You can run the calls asynchronously by adding a callback method
to the PendingResult
object,
which fires when the operation is completed:
pendingResult.setResultCallback(new ResultCallback<DataItemResult>() { @Override public void onResult(final DataItemResult result) { if(result.getStatus().isSuccess()) { Log.d(TAG, "Data item set: " + result.getDataItem().getUri()); } } });
Synchronous calls
If your code is running on a separate handler thread in a background service (which is the case
in a WearableListenerService
),
it's fine for the calls to block. In this case, you can call
await()
on the PendingResult
object, which blocks until the request completes and returns a
Result
object:
DataItemResult result = pendingResult.await(); if(result.getStatus().isSuccess()) { Log.d(TAG, "Data item set: " + result.getDataItem().getUri()); }
Listen for Data Layer Events
Because the data layer synchronizes and sends data across the handheld and wearable, it is usually necessary to listen for important events. Examples of such events include creation of data items and receipt of messages.
To listen for data layer events, you have two options:
- Create a service that extends
WearableListenerService
. - Create an activity that implements
DataApi.DataListener
.
With both these options, you override the data event callback methods for the events you are interested in handling.
With a WearableListenerService
You typically create instances of this service in both your wearable and handheld apps. If you are not interested in data events in one of these apps, then you don't need to implement this service in that particular 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 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 as follows:
-
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.
In addition to those on this list, you can listen for events from
ChannelApi.ChannelListener
, such as
onChannelOpened()
.
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 allows the system to bind your service as needed.
The following example shows how to implement a simple
WearableListenerService
:
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); } final List events = FreezableUtils .freezeIterable(dataEvents); GoogleApiClient googleApiClient = new GoogleApiClient.Builder(this) .addApi(Wearable.API) .build(); ConnectionResult connectionResult = googleApiClient.blockingConnect(30, TimeUnit.SECONDS); if (!connectionResult.isSuccess()) { Log.e(TAG, "Failed to connect to GoogleApiClient."); return; } // Loop through the events and send a message // to the node that created the data item. for (DataEvent event : events) { 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.MessageApi.sendMessage(googleApiClient, nodeId, DATA_ITEM_RECEIVED_PATH, payload); } } }
The next section explains how to use an intent filter with this listener.
Using filters with WearableListenerService
An intent filter for the
WearableListenerService
example shown in the previous section might look like this:
<service android:name=".DataLayerListenerService"> <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 application. This change improves system efficiency
and reduces battery consumption and other overhead associated with your
application. 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. If you are matching by path or path prefix, you must specify a wildcard or specific host. If you do not do so, the system ignores the path you specified.
For more information on the filter types that Wear 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, there are two important rules to remember:
- If a scheme is not specified for the intent filter, the system ignores all the other URI attributes.
- If no host is specified for the filter, the system ignores the port attribute and all the path attributes.
With a listener activity
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:
To create an activity that listens for data events:
- Implement the desired interfaces.
- In
onCreate()
, create an instance ofGoogleApiClient
to work with the Data Layer API. -
In
onStart()
, callconnect()
to connect the client to Google Play services. - When the connection to Google Play services is established, the system calls
onConnected()
. This is where you callDataApi.addListener()
,MessageApi.addListener()
, orCapabilityApi.addListener()
to notify Google Play services that your activity is interested in listening for data layer events. - In
onStop()
, unregister any listeners withDataApi.removeListener()
,MessageApi.removeListener()
, orCapabilityApi.removeListener()
. - Implement
onDataChanged()
,onMessageReceived()
,onCapabilityChanged()
, or methods from Channel API listener methods, depending on the interfaces that you implemented.
An alternative to adding listeners in
onConnected()
and removing them in
onStop()
is to add a filtered listener in an activity’s onResume()
and
remove it in onPause()
, so as to only receive data that is relevant to the
current application state.
Here's an example that implements
DataApi.DataListener
:
public class MainActivity extends Activity implements DataApi.DataListener, ConnectionCallbacks, OnConnectionFailedListener { private GoogleApiClient mGoogleApiClient; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mGoogleApiClient = new GoogleApiClient.Builder(this) .addApi(Wearable.API) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .build(); } @Override protected void onStart() { super.onStart(); if (!mResolvingError) { mGoogleApiClient.connect(); } } @Override public void onConnected(Bundle connectionHint) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Connected to Google Api Service"); } Wearable.DataApi.addListener(mGoogleApiClient, this); } @Override protected void onStop() { if (null != mGoogleApiClient && mGoogleApiClient.isConnected()) { Wearable.DataApi.removeListener(mGoogleApiClient, this); mGoogleApiClient.disconnect(); } super.onStop(); } @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()); } } } }
Using Filters with Listener Activities
Just as you can specify intent filters for manifest-based
WearableListenerService
objects, you can also use intent filters when registering a
listener through the Wearable API. The same rules are applicable to both
API-based listeners 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 to
remove the listener in the activity’s onPause()
method.
Implementing listeners in this fashion allows your application to more selectively receive events,
improving its design and efficiency.