Cómo migrar apps de 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 reemplazo
  • 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.

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 API 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 de Wear

Como ejemplo de migración, en los fragmentos de código a continuación, puedes ver cómo se actualizó 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 declaración de importación en el archivo MainActivity.java. Se debería quitar esta declaración 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 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 utilizando 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 API de Wearable y Tasks, consulta la muestra de Data Layer de Wear. Además, como ejemplo del uso de tareas pesadas del procesamiento de IU o en un servicio, hay otra opción disponible. A continuación, puedes ver un ejemplo de cómo 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);
      }
    }