Cómo manejar eventos de Data Layer en Wear

Cuando haces una llamada a la API de Data Layer, puedes recibir el estado de la llamada cuando se completa. También puedes escuchar eventos de datos resultantes de modificaciones de datos que haga tu app en cualquier punto de la red de Wear OS by Google.

Nota: Una app para Wear puede comunicarse con una para teléfonos usando la API de Data Layer, pero no es recomendable conectarse a una red con esta API.

Consulta los siguientes recursos relacionados:

Espera el estado de las llamadas a Data Layer

Las llamadas a la API de Data Layer, como una llamada con el método putDataItem de la clase DataClient, en ocasiones pueden mostrar un objeto Task<ResultType>. Apenas se crea el objeto Task, se pone en cola la operación en segundo plano. Si no realizas más acciones, la operación se completa en silencio. Sin embargo, normalmente querrás hacer algo con el resultado después de que finalice la operación. El objeto Task te permite esperar el estado del resultado, ya sea de forma síncrona o asíncrona.

Llamadas asíncronas

Si tu código se ejecuta en el procesamiento de IU principal, no realices llamadas de bloqueo a la API de Data Layer. Puedes ejecutar las llamadas de manera asíncrona si agregas un método de devolución de llamada al objeto Task, que se activa cuando la operación finaliza:

Kotlin

    // Using Kotlin function references
    task.addOnSuccessListener(::handleDataItem)
    task.addOnFailureListener(::handleDataItemError)
    task.addOnCompleteListener(::handleTaskComplete)
    ...
    fun handleDataItem(dataItem: DataItem) { ... }
    fun handleDataItemError(exception: Exception) { ... }
    fun handleTaskComplete(task: Task<DataItem>) { ... }
    

Java

    // Using Java 8 Lambdas.
    task.addOnSuccessListener(dataItem -> handleDataItem(dataItem));
    task.addOnFailureListener(exception -> handleDataItemError(exception));
    task.addOnCompleteListener(task -> handleTaskComplete(task));
    

Consulta la API de Tasks para ver otras opciones, como la posibilidad de encadenar diferentes tareas.

Llamadas síncronas

Si se ejecuta tu código en un subproceso de controlador separado en un servicio en segundo plano (como sucede con WearableListenerService), está bien que se bloqueen las llamadas. En este caso, puedes llamar a Tasks.await() en el objeto Task, que se bloquea hasta que se completa la solicitud y se muestra un objeto Result:

Kotlin

    try {
        Tasks.await(dataItemTask).apply {
            Log.d(TAG, "Data item set: $uri")
        }
    }
    catch (e: ExecutionException) { ... }
    catch (e: InterruptedException) { ... }
    

Java

    try {
        DataItem item = Tasks.await(dataItemTask);
        Log.d(TAG, "Data item set: " + item.getUri());
    } catch (ExecutionException | InterruptedException e) {
      ...
    }
    

Escucha eventos de Data Layer

Como Data Layer se sincroniza y envía datos a través del dispositivo portátil y del wearable, generalmente es necesario escuchar eventos importantes, como la creación de elementos de datos y la recepción de mensajes.

Para escuchar los eventos de Data Layer, tienes dos opciones:

Con estas dos opciones, anulas los métodos de devolución de llamada de eventos de datos para los eventos que te interesa manejar.

Nota: Con respecto del uso de batería, WearableListenerService está registrado en el manifiesto de una app y puede iniciarla si aún no se está ejecutando. Si solo necesitas escuchar eventos cuando la app ya se está ejecutando, lo que suele suceder con las aplicaciones interactivas, no uses un WearableListenerService. En su lugar, registra un objeto de escucha en vivo usando, por ejemplo, el método addListener de la clase DataClient. De esta forma, se puede reducir la carga en el sistema y el uso de batería.

Con un WearableListenerService

Por lo general, creas instancias de este servicio en tus apps para wearables y dispositivos portátiles. Si no te interesan los eventos de datos en una de estas apps, no es necesario que implementes el servicio en la app en cuestión.

Por ejemplo, puedes tener una para dispositivos portátiles que establece y obtiene objetos de elementos de datos y una app para wearables que escucha estas actualizaciones para actualizar su IU. El wearable nunca actualiza los elementos de datos, por lo que la app para dispositivos portátiles no escucha ningún evento de datos de la app para wearables.

Estos son algunos de los eventos que puedes escuchar usando WearableListenerService:

  • onDataChanged(): Cada vez que se crea, borra o cambia un objeto de elemento de datos, el sistema activa esta devolución de llamada en todos los nodos conectados.
  • onMessageReceived(): Un mensaje enviado desde un nodo activa esta devolución de llamada en el nodo de destino.
  • onCapabilityChanged(): Cuando una función que anuncia una instancia de tu app está disponible en la red, ese evento activa esta devolución de llamada. Si buscas un nodo cercano, puedes enviar una consulta al método isNearby() de los nodos que se proporcionan en la devolución de llamada.

Además de los que se incluyen en esta lista, puedes escuchar eventos de ChannelClient.ChannelCallback, como onChannelOpened().

Todos los eventos anteriores se ejecutan en un subproceso en segundo plano, no en el subproceso principal.

Para crear un WearableListenerService, sigue estos pasos:

  1. Crea una clase que extienda WearableListenerService.
  2. Escucha los eventos que te interesen, como onDataChanged().
  3. Declara un filtro de intent en tu manifiesto de Android para informar al sistema sobre tu WearableListenerService. Esta declaración le permite al sistema vincular tu servicio según sea necesario.

En el siguiente ejemplo, se muestra cómo implementar un WearableListenerService simple:

Kotlin

    private const val TAG = "DataLayerSample"
    private const val START_ACTIVITY_PATH = "/start-activity"
    private const val DATA_ITEM_RECEIVED_PATH = "/data-item-received"

    class DataLayerListenerService : WearableListenerService() {

        override fun onDataChanged(dataEvents: DataEventBuffer) {
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "onDataChanged: $dataEvents")
            }

            // Loop through the events and send a message
            // to the node that created the data item.
            dataEvents.map { it.dataItem.uri }
                    .forEach { uri ->
                        // Get the node id from the host value of the URI
                        val nodeId: String = uri.host
                        // Set the data of the message to be the bytes of the URI
                        val payload: ByteArray = uri.toString().toByteArray()

                        // Send the RPC
                        Wearable.getMessageClient(this)
                                .sendMessage(nodeId, DATA_ITEM_RECEIVED_PATH, payload)
                    }
        }
    }
    

Java

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

            // Loop through the events and send a message
            // to the node that created the data item.
            for (DataEvent event : dataEvents) {
                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.getMessageClient(this).sendMessage(
                      nodeId,  DATA_ITEM_RECEIVED_PATH, payload);
            }
        }
    }
    

En la siguiente sección, se explica cómo usar un filtro de intent con este objeto de escucha.

Usa filtros con WearableListenerService

Un filtro de intent para el ejemplo de WearableListenerService que se mostró en la sección previa puede verse de esta manera:

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

En este filtro, la acción DATA_CHANGED reemplaza la acción BIND_LISTENER que se recomendó anteriormente para que solo ciertos eventos activen o inicien tu app. Este cambio mejora la eficiencia del sistema y reduce el consumo de batería y otras sobrecargas asociadas con tu app. En este ejemplo, el reloj escucha el elemento de datos /start-activity y el teléfono escucha la respuesta del mensaje /data-item-received.

Se aplican las reglas de coincidencia de filtro estándar de Android. Puedes especificar varios servicios por manifiesto, varios filtros de intent por servicio, varias acciones por filtro y varias estrofas de datos por filtro. Los filtros pueden coincidir en un host comodín o en un host determinado. Para que coincidan en un host comodín, usa host="*". Para que coincidan en un host determinado, especifica host=<node_id>.

También puede hacer coincidir una ruta de acceso literal o un prefijo de ruta. Si realizas la coincidencia por ruta de acceso o prefijo de ruta, debes especificar un comodín o un host determinado. Si no lo haces, el sistema ignora la ruta de acceso que especificaste.

Para obtener más información sobre los tipos de filtros compatibles con Wear, consulta la documentación de referencia de la API para WearableListenerService.

Para obtener más información sobre los filtros de datos y las reglas de coincidencias, consulta la documentación de referencia de la API para el elemento de manifiesto data.

Al hacer coincidir filtros de intent, debes recordar dos reglas importantes:

  • Si no se especifica un esquema para el filtro de intent, el sistema ignora todos los demás atributos del URI.
  • Si no se especifica ningún host para el filtro, el sistema ignora todos los atributos de la ruta de acceso.

Con un objeto de escucha activo

Si tu app se centra en los eventos de Data Layer cuando el usuario está interactuando con ella, es posible que no necesites un servicio prolongado para manejar cada cambio de datos. En tal caso, puedes escuchar eventos en una actividad si implementas una o más de las siguientes interfaces:

Para crear una actividad que escucha eventos de datos, haz lo siguiente:

  1. Implementa las interfaces deseadas.
  2. En el método onCreate() o en onResume(), llama a Wearable.getDataClient(this).addListener(), MessageClient.addListener(), CapabilityClient.addListener() o ChannelClient.registerChannelCallback() para informar a Servicios de Google Play que tu actividad desea escuchar eventos de Data Layer.
  3. En onStop() o en onPause(), anula el registro de los objetos de escucha con DataClient.removeListener(), MessageClient.removeListener(), CapabilityClient.removeListener() o ChannelClient.unregisterChannelCallback().
  4. Si una actividad solo se interesa en eventos con un prefijo específico de ruta de acceso, puedes agregar un objeto de escucha con un filtro de prefijo adecuado y así recibir solo los datos relevantes para el estado actual de la app.
  5. Implementa onDataChanged(), onMessageReceived(), onCapabilityChanged() o métodos de ChannelClient.ChannelCallback, según las interfaces que hayas implementado. A estos métodos se los llama en el subproceso principal, pero puedes especificar un Looper personalizado usando WearableOptions.

En el siguiente ejemplo, se implementa DataClient.OnDataChangedListener:

Kotlin

    class MainActivity : Activity(), DataClient.OnDataChangedListener {

        public override fun onResume() {
            Wearable.getDataClient(this).addListener(this)
        }

        override fun onPause() {
            Wearable.getDataClient(this).removeListener(this)
        }

        override fun onDataChanged(dataEvents: DataEventBuffer) {
            dataEvents.forEach { event ->
                if (event.type == DataEvent.TYPE_DELETED) {
                    Log.d(TAG, "DataItem deleted: " + event.dataItem.uri)
                } else if (event.type == DataEvent.TYPE_CHANGED) {
                    Log.d(TAG, "DataItem changed: " + event.dataItem.uri)
                }
            }
        }
    }
    

Java

    public class MainActivity extends Activity implements DataClient.OnDataChangedListener {

        @Override
        public void onResume() {
            Wearable.getDataClient(this).addListener(this);
        }

        @Override
        protected void onPause() {
            Wearable.getDataClient(this).removeListener(this);
        }

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

Usa filtros con objetos de escucha activos

Como se señaló anteriormente en esta página, del mismo modo en que puedes especificar filtros de intent para objetos WearableListenerService basados en manifiestos, también puedes usar filtros de intent cuando registras un objeto de escucha en vivo mediante la API de Wearable. Se aplican las mismas reglas a los objetos de escucha en vivo basados en API y a los basados en manifiestos.

Un patrón común es registrar un objeto de escucha con una ruta de acceso específica o un prefijo de ruta en el método onResume() de una actividad y quitar el objeto de escucha en el método onPause() de la actividad. Implementar objetos de escucha de este modo permite que tu app reciba eventos de manera más selectiva, lo que mejora su diseño y eficiencia.