Cómo migrar apps para Wear a GoogleApi

A partir de la versión 11.8.0 de los Servicios de Google Play, se deben migrar las apps para Wear OS de la clase GoogleApiClient y usar, en cambio, objetos de clientes basados en la clase GoogleApi.

El uso de GoogleApi facilita la configuración de operaciones asíncronas. Por ejemplo, como se describe en la introducción a la API de Tasks, puedes obtener un objeto Task en lugar de un objeto PendingResult.

En esta página, se incluye lo siguiente:

  • Una tabla de componentes de repuesto
  • Un ejemplo de actualización de una app existente para usar la API de Tasks

Nota: Esta actualización no se aplica a las apps de Wear OS para China, que generalmente usan la versión 10.2.0 de los Servicios de Google Play.

Nota: Por el momento, esta API solo está disponible en teléfonos Android y relojes Wear OS vinculados a teléfonos Android. En el caso de los relojes con Wear OS vinculados a teléfonos iOS, las apps pueden consultar otras APIs basadas en la nube si hay conectividad a Internet disponible.

Reemplazos para componentes obsoletos

Cuando usas clases que extienden la clase GoogleApi, como DataClient y MessageClient, el SDK de Servicios de Google Play administra por ti las conexiones a estos servicios.

Las apps que usan las clases de reemplazo que se indican a continuación no necesitan crear y administrar objetos GoogleApiClient. Consulta también Cómo acceder a las APIs de Google y la página de referencia para la clase Wearable.

En la siguiente tabla, se incluyen los componentes que dejaron de estar disponibles y sus reemplazos:

Componente obsoleto Componente de reemplazo
CapabilityApi CapabilityClient
Channel ChannelClient.Channel
ChannelApi ChannelClient
DataApi DataClient
MessageApi MessageClient
NodeApi NodeClient

Además, ten en cuenta lo siguiente:

Ejemplo de migración de una app para Wear

Como ejemplo de migración, en los fragmentos de código a continuación, puedes ver la actualización de la muestra de Data Layer de Wear, que usa la API de Data Layer, para la versión 11.8.0 de los Servicios de Google Play. Si tu app tiene un módulo de teléfono, es posible que sus actualizaciones sean similares a las del módulo de Wear.

Cómo actualizar la dependencia de los Servicios de Google Play

Debido a que es posible que tu app dependa de una versión anterior de los Servicios de Google Play, actualiza la siguiente dependencia en el archivo build.gradle de tu módulo de Wear:

dependencies {
...
compile 'com.google.android.gms:play-services-wearable:11.8.0'
}

Cómo actualizar las declaraciones de importación de tu app

Importa las clases necesarias, incluidas las clases en la API de Tasks.

Por ejemplo, antes la muestra de Data Layer de Wear incluía la siguiente sentencia de importación en el archivo MainActivity.java. Se debería quitar esta sentencia de import:

Kotlin

...
import com.google.android.gms.common.api.GoogleApiClient
...

Java

...
import com.google.android.gms.common.api.GoogleApiClient;
...

En la muestra de Data Layer de Wear, se reemplazaron las declaraciones de import como la anterior, por ejemplo, con la siguiente (la segunda sirve para administrar excepciones de tareas):

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;
...

Cómo implementar las nuevas interfaces de cliente

Quita cualquier uso de la clase GoogleApiClient y las interfaces asociadas (ConnectionCallbacks, OnConnectionFailedListener, etc.) y reemplaza las otras implementaciones de objetos de escucha con sus versiones nuevas. Por lo general, los métodos reales para anular tienen el mismo nombre que antes, de manera que el cambio principal es similar al ejemplo que se muestra a continuación.

La actividad principal de la muestra de Data Layer de Wear (como se indica en un informe sobre diferencias en GitHub) había implementado, por ejemplo, la interfaz de CapabilityApi.CapabilityListener. Sin embargo, ahora la actividad principal implementa CapabilityClient.OnCapabilityChangedListener.

A continuación, se muestra una comparación de las definiciones de clase.

A continuación, puedes ver un fragmento antes del uso de la versión 11.8.0 de los Servicios de Google Play:

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

Aquí puedes ver un fragmento después del uso de la versión 11.8.0 de los Servicios de Google Play:

Kotlin

class MainActivity :
        Activity(),
        DataClient.OnDataChangedListener,
        MessageClient.OnMessageReceivedListener,
        CapabilityClient.OnCapabilityChangedListener

Java

public class MainActivity extends Activity implements
  DataClient.OnDataChangedListener,
  MessageClient.OnMessageReceivedListener,
  CapabilityClient.OnCapabilityChangedListener

Cómo quitar y agregar objetos de escucha

Los objetos de cliente nuevos se almacenan en caché y se comparten entre las instancias de GoogleApi. Por lo tanto, no es necesario mantener variables de los miembros; no es costoso crear los clientes, y estos no perderán sus objetos de escucha.

A continuación, se muestra un fragmento de la muestra de Data Layer de Wear revisada:

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);
}

Cómo solicitar información con la API de Tasks

Es posible que desees solicitar información fuera de los objetos de escucha que actualizan tu app cuando cambian los datos. En estos casos, realiza una solicitud en la que se use un cliente como DataClient, además de la API de Tasks y una clase de resultado (es decir, como Task<ResultType>).

Por ejemplo, como puedes ver en la muestra de Data Layer de Wear, puedes usar la API de Tasks para encontrar nodos conectados con cualquier función:

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();
}

Si deseas obtener un código adicional que usa las APIs de Wearable y Tasks, consulta la muestra de Data Layer de Wear. Además, como ejemplo del uso de tareas pesadas del subproceso de IU o en un servicio, hay otra opción disponible. A continuación, puedes ver un ejemplo para bloquear una tarea y obtener el resultado de manera síncrona:

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);
  }
}