Starting with version
11.8.0 of Google Play services, Wear OS apps should migrate away
from the GoogleApiClient
class and instead use client objects that are based on the
GoogleApi
class.
Use of
GoogleApi
makes it easier to set up asynchronous operations.
For example, as described in the introduction to the
Tasks API, you can obtain a Task
object instead of a
PendingResult
object.
This page includes:
- A table of replacement components
- An example of updating an existing app to use the Tasks API
Note: This update does not apply to Wear OS apps for China, which generally use version 10.2.0 of Google Play services.
Note: This API is currently only available on Android phones and Wear OS watches that are paired with Android phones. For Wear OS watches paired with iOS phones, apps can query other cloud-based APIs if Internet connectivity is available.
Replacements for deprecated components
When you use classes that extend the
GoogleApi
class, such as
DataClient
and
MessageClient
, the Google Play services SDK manages
connections to Google Play services for you.
Apps that use the replacement classes below do not need to create and
manage GoogleApiClient
objects. Also see Accessing
Google APIs and the
reference page for the Wearable class.
The following table contains deprecated components and their replacements:
Deprecated component | Replacement component |
CapabilityApi
|
CapabilityClient
|
Channel
|
ChannelClient.Channel
|
ChannelApi
|
ChannelClient
|
DataApi
|
DataClient
|
MessageApi
|
MessageClient
|
NodeApi
|
NodeClient
|
Also note the following:
- For notifications of changes to channels,
Channel.ChannelListener
is replaced byChannelClient.ChannelCallback
- For setting the thread for listener callbacks,
GoogleApiClient.Builder.setHandler
is replaced by thesetLooper
method ofWearableOptions.Builder
Migration example for a Wear app
As a migration example, the code snippets below illustrate how the Wear Data Layer sample, which uses the Data Layer API, was updated for version 11.8.0 of Google Play services. If your app has a phone module, its updates may be similar to those for the Wear module.
Update the dependency on Google Play services
Since your app may depend on an earlier version of Google Play services,
update the following dependency in the build.gradle
file of
your Wear module:
dependencies { ... compile 'com.google.android.gms:play-services-wearable:11.8.0' }
Update the import statements of your app
Import the necessary classes, including classes in the Tasks API.
For example, formerly the Wear Data Layer
sample included the following import statement in the
MainActivity.java
file. This import
statement
should be removed:
Kotlin
... import com.google.android.gms.common.api.GoogleApiClient ...
Java
... import com.google.android.gms.common.api.GoogleApiClient; ...
In the Wear
Data Layer sample, import
statements such as the above
were replaced with, for example, the following (the second one is for
handling task exceptions):
Kotlin
... import com.google.android.gms.tasks.Tasks import java.util.concurrent.ExecutionException ...
Java
... import com.google.android.gms.tasks.Tasks; import java.util.concurrent.ExecutionException; ...
Implement the new client interfaces
Remove any usage of the GoogleApiClient
class and associated interfaces (ConnectionCallbacks
,
OnConnectionFailedListener
, etc.) and replace the other
Listener implementations with their new versions. The actual methods to
override generally have the same names as before, so the main change is
similar to the example below.
The main activity of the Wear Data Layer sample (as indicated in a diff
on
GitHub) had implemented, for example, the
CapabilityApi.CapabilityListener
interface. But now, the
main activity implements
CapabilityClient.OnCapabilityChangedListener
.
Below is a comparison of the class definitions.
Here is a snippet before the use of version 11.8.0 of Google Play services:
Kotlin
class MainActivity : Activity(), GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, DataApi.DataListener, MessageApi.MessageListener, CapabilityApi.CapabilityListener
Java
public class MainActivity extends Activity implements ConnectionCallbacks, OnConnectionFailedListener, DataApi.DataListener, MessageApi.MessageListener, CapabilityApi.CapabilityListener
Here is a snippet after the use of version 11.8.0 of Google Play services:
Kotlin
class MainActivity : Activity(), DataClient.OnDataChangedListener, MessageClient.OnMessageReceivedListener, CapabilityClient.OnCapabilityChangedListener
Java
public class MainActivity extends Activity implements DataClient.OnDataChangedListener, MessageClient.OnMessageReceivedListener, CapabilityClient.OnCapabilityChangedListener
Remove and add listeners
The new client objects are cached and shared between
GoogleApi
instances, so it is unnecessary to keep member
variables; clients are inexpensive to create and won't lose their
listeners.
Below is a snippet from the revised Wear Data Layer sample:
Kotlin
override fun onResume() { super.onResume() Wearable.getDataClient(this).addListener(this) Wearable.getMessageClient(this).addListener(this) Wearable.getCapabilityClient(this) .addListener( this, Uri.parse("wear://"), CapabilityClient.FILTER_REACHABLE ) } override fun onPause() { super.onPause() Wearable.getDataClient(this).removeListener(this) Wearable.getMessageClient(this).removeListener(this) Wearable.getCapabilityClient(this).removeListener(this) }
Java
@Override protected void onResume() { super.onResume(); Wearable.getDataClient(this).addListener(this); Wearable.getMessageClient(this).addListener(this); Wearable.getCapabilityClient(this) .addListener( this, Uri.parse("wear://"), CapabilityClient.FILTER_REACHABLE); } @Override protected void onPause() { super.onPause(); Wearable.getDataClient(this).removeListener(this); Wearable.getMessageClient(this).removeListener(this); Wearable.getCapabilityClient(this).removeListener(this); }
Request information with the Tasks API
You may want to request information outside of the listeners that update
your app when there is a data change. In such cases, make a request using
a client such as
DataClient
, in conjunction with the Tasks API and
a result class (that is, as Task<ResultType>
).
For example, as shown in the Wear Data Layer sample, you can use the Tasks API to find connected nodes with any given capabilities:
Kotlin
private fun showNodes(vararg capabilityNames: String) { Wearable.getCapabilityClient(this) .getAllCapabilities(CapabilityClient.FILTER_REACHABLE).apply { addOnSuccessListener { capabilityInfoMap -> val nodes: Set<Node> = capabilityInfoMap .filter { capabilityNames.contains(it.key) } .flatMap { it.value.nodes } .toSet() showDiscoveredNodes(nodes) } } } private fun showDiscoveredNodes(nodes: Set<Node>) { val nodesList: Set<String> = nodes.map { it.displayName }.toSet() val msg: String = if (nodesList.isEmpty()) { Log.d(TAG, "Connected Nodes: No connected device was found for the given capabilities") getString(R.string.no_device) } else { Log.d(TAG, "Connected Nodes: ${nodesList.joinToString(separator = ", ")}") getString(R.string.connected_nodes, nodesList) } Toast.makeText(this@MainActivity, msg, Toast.LENGTH_LONG).show() }
Java
private void showNodes(final String... capabilityNames) { Task<Map<String, CapabilityInfo>> capabilitiesTask = Wearable.getCapabilityClient(this) .getAllCapabilities(CapabilityClient.FILTER_REACHABLE); capabilitiesTask.addOnSuccessListener(new OnSuccessListener<Map<String, CapabilityInfo>>() { @Override public void onSuccess(Map<String, CapabilityInfo> capabilityInfoMap) { Set<Node> nodes = new HashSet<>(); if (capabilityInfoMap.isEmpty()) { showDiscoveredNodes(nodes); return; } for (String capabilityName : capabilityNames) { CapabilityInfo capabilityInfo = capabilityInfoMap.get(capabilityName); if (capabilityInfo != null) { nodes.addAll(capabilityInfo.getNodes()); } } showDiscoveredNodes(nodes); } }); } private void showDiscoveredNodes(Set<Node> nodes) { List<String> nodesList = new ArrayList<>(); for (Node node : nodes) { nodesList.add(node.getDisplayName()); } LOGD(TAG, "Connected Nodes: " + (nodesList.isEmpty() ? "No connected device was found for the given capabilities" : TextUtils.join(",", nodesList))); String msg; if (!nodesList.isEmpty()) { msg = getString(R.string.connected_nodes, TextUtils.join(", ", nodesList)); } else { msg = getString(R.string.no_device); } Toast.makeText(MainActivity.this, msg, Toast.LENGTH_LONG).show(); }
For additional code that utilizes the Wearable and Tasks APIs, see the Wear Data Layer sample. And as an example of using heavy tasks off of the UI thread or in a service, there is another option available. Here is an example of how to block on a task and get the result synchronously:
Kotlin
override fun doInBackground(vararg params: Asset): Bitmap? { if (params.isNotEmpty()) { val asset = params[0] val getFdForAssetResponseTask: Task<DataClient.GetFdForAssetResponse> = Wearable.getDataClient(applicationContext).getFdForAsset(asset) return try { // Block on a task and get the result synchronously. This is generally done // when executing a task inside a separately managed background thread. Doing // this on the main (UI) thread can cause your application to become // unresponsive. val getFdForAssetResponse: DataClient.GetFdForAssetResponse = Tasks.await(getFdForAssetResponseTask) getFdForAssetResponse.inputStream?.let { assetInputStream -> BitmapFactory.decodeStream(assetInputStream) } ?: run { Log.w(TAG, "Requested an unknown Asset.") null } } catch (exception: ExecutionException) { Log.e(TAG, "Failed retrieving asset, Task failed: $exception") return null } catch (exception: InterruptedException) { Log.e(TAG, "Failed retrieving asset, interrupt occurred: $exception") return null } } else { Log.e(TAG, "Asset must be non-null") return null } } override fun onPostExecute(bitmap: Bitmap?) { bitmap?.also { Log.d(TAG, "Setting background image on second page..") moveToPage(1) assetFragment.setBackgroundImage(it) } }
Java
@Override protected Bitmap doInBackground(Asset... params) { if (params.length > 0) { Asset asset = params[0]; Task<DataClient.GetFdForAssetResponse> getFdForAssetResponseTask = Wearable.getDataClient(getApplicationContext()).getFdForAsset(asset); try { // Block on a task and get the result synchronously. This is generally done // when executing a task inside a separately managed background thread. Doing // this on the main (UI) thread can cause your application to become // unresponsive. DataClient.GetFdForAssetResponse getFdForAssetResponse = Tasks.await(getFdForAssetResponseTask); InputStream assetInputStream = getFdForAssetResponse.getInputStream(); if (assetInputStream != null) { return BitmapFactory.decodeStream(assetInputStream); } else { Log.w(TAG, "Requested an unknown Asset."); return null; } } catch (ExecutionException exception) { Log.e(TAG, "Failed retrieving asset, Task failed: " + exception); return null; } catch (InterruptedException exception) { Log.e(TAG, "Failed retrieving asset, interrupt occurred: " + exception); return null; } } else { Log.e(TAG, "Asset must be non-null"); return null; } } @Override protected void onPostExecute(Bitmap bitmap) { if (bitmap != null) { LOGD(TAG, "Setting background image on second page.."); moveToPage(1); assetFragment.setBackgroundImage(bitmap); } }