Migrar apps Wear para GoogleApi

A partir da versão 11.8.0 do Google Play Services, os apps para Wear OS abandonarão o uso da classe GoogleApiClient e, no lugar dela, usarão objetos de cliente baseados na classe GoogleApi.

O uso de GoogleApi facilita a configuração de operações assíncronas. Por exemplo, conforme descrito na introdução à API Tasks, você pode conseguir um objeto Task em vez de um objeto PendingResult.

Esta página inclui:

  • Uma tabela de componentes de substituição
  • Um exemplo de atualização de um app já existente para usar a API Tasks

Observação: essa atualização não se aplica a apps para Wear OS para a China, que geralmente usam a versão 10.2.0 do Google Play Services.

Substituições de componentes obsoletos

Quando você usa classes que estendem a classe GoogleApi, como DataClient e MessageClient, o SDK do Google Play Services gerencia as conexões com o Google Play Services para você.

Os apps que usam as classes de substituição abaixo não precisam criar e gerenciar objetos GoogleApiClient. Consulte também Como acessar APIs Google e a página de referência da classe Wearable.

A tabela a seguir contém componentes obsoletos e as substituições deles:

Componente obsoleto Componente de substituição
CapabilityApi CapabilityClient
Channel ChannelClient.Channel
ChannelApi ChannelClient
DataApi DataClient
MessageApi MessageClient
NodeApi NodeClient

Observe também o seguinte:

Exemplo de migração para um app Wear

Como exemplo de migração, os snippets de código abaixo ilustram como a amostra da camada de dados do Wear (link em inglês), que usa a API Data Layer, foi atualizada para a versão 11.8.0 do Google Play Services. Se seu app tiver um módulo de telefone, as atualizações dele poderão ser semelhantes às do módulo Wear.

Atualizar a dependência no Google Play Services

Como seu app pode depender de uma versão anterior do Google Play Services, atualize a seguinte dependência no arquivo build.gradle do seu módulo Wear:

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

Atualizar as declarações de importação do seu app

Importe as classes necessárias, incluindo as classes na API Tasks.

Por exemplo, a amostra da camada de dados do Wear (link em inglês) costumava incluir a declaração de importação a seguir no arquivo MainActivity.java. Essa declaração de import precisa ser removida:

Kotlin

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

Java

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

Na amostra da camada de dados do Wear (link em inglês), as instruções de import como a mostrada acima foram substituídas, por exemplo, pela mostrada a seguir (a segunda é para lidar com exceções de tarefas):

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

Implementar as novas interfaces do cliente

Remova qualquer uso da classe GoogleApiClient e interfaces associadas (ConnectionCallbacks, OnConnectionFailedListener etc.) e substitua as outras implementações do Listener pelas novas versões. Os métodos reais para substituir costumam ter os mesmos nomes de antes, então a alteração principal é semelhante ao exemplo abaixo.

A principal atividade da amostra da camada de dados do Wear (como indicado em uma diferença no GitHub, link em inglês) implementou, por exemplo, a interface CapabilityApi.CapabilityListener. Agora, no entanto, a atividade principal implementa CapabilityClient.OnCapabilityChangedListener.

Veja abaixo uma comparação das definições de classe.

Veja um snippet antes do uso da versão 11.8.0 do 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
    

Veja um snippet depois do uso da versão 11.8.0 do 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
    

Remover e adicionar listeners

Os novos objetos do cliente são armazenados em cache e compartilhados entre instâncias de GoogleApi, então é desnecessário manter variáveis de membro. Os clientes têm baixo custo de criação e não perdem os listeners.

Veja abaixo um snippet da amostra revisada da camada de dados do Wear (link em inglês):

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

Solicitar informações com a API Tasks

Você pode solicitar informações de fora dos listeners que atualizam seu app quando há uma alteração de dados. Nesses casos, faça uma solicitação usando um cliente como o DataClient em conjunto com a API Tasks e uma classe de resultado (ou seja, como Task<ResultType>).

Por exemplo, como mostrado na amostra de camada de dados do Wear, você pode usar a API Tasks para encontrar nós conectados com qualquer recurso:

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

Para conseguir um outro código que utilize as APIs Wearable e Tasks, consulte a amostra da camada de dados do Wear (link em inglês). E como um exemplo de uso de tarefas pesadas fora da linha de execução da IU ou em um serviço, há outra opção disponível. Veja este exemplo de como bloquear uma tarefa e conseguir o resultado de maneira 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);
      }
    }