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.

Para ver un ejemplo de cómo trabajar de manera efectiva con la API de Data Layer, consulta la app de ejemplo de Android DataLayer Sample.

Espera el estado de las llamadas a Data Layer

Las llamadas a la API de Data Layer, por ejemplo, una llamada que usa el método putDataItem de la DataClient clase, a veces muestran un objeto Task<ResultType>. En cuanto se crea el objeto Task, la operación se pone en cola en segundo plano. Si no haces nada más después de esto, la operación se completa de forma silenciosa.

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 subproceso de IU principal, no realices llamadas de bloqueo a la API de Data Layer y usa una corrutina para llamar a putDataItem:

private suspend fun Context.sendDataAsync(count: Int) {
    try {
        val putDataReq: PutDataRequest = PutDataMapRequest.create("/count").run {
            dataMap.putInt("count_key", count)
            asPutDataRequest()
        }
        val dataItem = Wearable.getDataClient(this).putDataItem(putDataReq).await()
        handleDataItem(dataItem)
    } catch (e: Exception) {
        handleDataItemError(e)
    } finally {
        handleTaskComplete()
    }
}

private fun handleDataItem(dataItem: DataItem) { }
private fun handleDataItemError(exception: Exception) { }
private fun handleTaskComplete() { }

Consulta la API de Task para ver otras opciones, como la de encadenar la ejecución de 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, usa runBlocking para realizar una llamada de bloqueo a putDataItem.

Nota: No llames a este método mientras estés en el subproceso principal.

private fun Context.sendDataSync(count: Int) = runBlocking {
    val putDataReq = PutDataMapRequest.create("/count").run {
        dataMap.putInt("count_key", count)
        asPutDataRequest()
    }

    try {
        val result = Wearable.getDataClient(this@sendDataSync)
            .putDataItem(putDataReq)
            .await()
        // Logic for success
    } catch (e: Exception) {
        // Handle failure
    }
}

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 necesitarás 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: Ten en cuenta el uso de batería de la app a la hora de elegir una implementación del objeto de escucha. Un WearableListenerService se registra en el manifiesto de la app y puede iniciarla si aún no se está ejecutando. Si solo necesitas escuchar eventos cuando tu 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 activo. Por ejemplo, usa el método addListener de la DataClient clase. Esto puede reducir la carga en el sistema y el uso de batería.

Usa un WearableListenerService

Por lo general, creas instancias de WearableListenerService en tus apps para wearables y dispositivos portátiles. Sin embargo, si no te interesan los eventos de datos en una de las apps, no necesitas implementar el servicio en esa app.

Por ejemplo, puedes tener una app para dispositivos portátiles que establezca y obtenga objetos de elementos de datos, y una app para wearables que escuche estas actualizaciones para actualizar su IU. La app para wearables nunca actualiza los elementos de datos, por lo que la app para dispositivos portátiles no escuchará ningún evento de datos de la app para wearables.

Algunos de los eventos que puedes escuchar con WearableListenerService son los siguientes:

  • onDataChanged(): Cada vez que se crea, se borra o se 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 capacidad 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 consultar el isNearby() método de los nodos que se proporcionan en la devolución de llamada.

También 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 interesan, como onDataChanged().
  3. Declara un filtro de intents 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:

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

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

Usa filtros con WearableListenerService

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

<service
    android:name=".snippets.datalayer.DataLayerListenerService"
    android:exported="true"
    tools:ignore="ExportedService" >
    <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>

El filtro de acción DATA_CHANGED le indica al sistema que tu app está interesada en los eventos de Data Layer.

En este ejemplo, el reloj escucha el elemento de datos /start-activity, y el teléfono escucha la respuesta del mensaje /data-item-received (DATA_ITEM_RECEIVED_PATH).

Se aplican las reglas de coincidencia de filtro estándar de Android. Puedes especificar varios servicios por manifiesto, varios filtros de intents 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 hacer que coincidan en un host comodín, usa host="*". Para que coincidan en un host determinado, especifica host=<node_id>.

También puedes hacer coincidir una ruta de acceso literal o un prefijo de ruta. Para ello, debes especificar un comodín o un host determinado. De lo contrario, el sistema ignorará la ruta que especifiques.

Para obtener más información sobre los tipos de filtros que admite Wear OS, 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 coincidencia, consulta la documentación de referencia de la API para el <data> elemento del manifiesto.

Cuando hagas coincidir filtros de intents, recuerda dos reglas importantes:

  • Si no se especifica ningún esquema para el filtro de intents, 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.

Usa 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 ese caso, puedes escuchar eventos en una actividad.

Para un enfoque más limpio y seguro, usa un observador del ciclo de vida. Si usas un observador del ciclo de vida, mueves la lógica de registro de la actividad LifecycleResumeEvent a una clase separada y reutilizable que implementa DefaultLifecycleObserver.

Este enfoque mantiene tu actividad simple y evita errores comunes, como olvidar anular el registro del objeto de escucha.

1. Crea el objeto de escucha optimizado para el ciclo de vida

Esta clase encapsula DataClient.OnDataChangedListener y administra automáticamente su propia suscripción en función del ciclo de vida de la actividad.

class WearDataLayerObserver(
    private val dataClient: DataClient,
    private val onDataReceived: (DataEventBuffer) -> Unit
) : DefaultLifecycleObserver, DataClient.OnDataChangedListener {

    // Implementation of the DataClient listener
    override fun onDataChanged(dataEvents: DataEventBuffer) {
        onDataReceived(dataEvents)
    }

    // Automatically register when the Activity starts
    override fun onResume(owner: LifecycleOwner) {
        dataClient.addListener(this)
    }

    // Automatically unregister when the Activity pauses
    override fun onPause(owner: LifecycleOwner) {
        dataClient.removeListener(this)
    }
}

2. Uso en tu actividad

Ahora, tu actividad no necesita usar LifecycleResumeEvent ni onPause para la API de Wear. Registras el observador una vez en LaunchedEvent (o onCreate).

class DataLayerLifecycleActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val dataClient = Wearable.getDataClient(this)

        // Create the observer and link it to the activity's lifecycle
        val wearObserver = WearDataLayerObserver(dataClient) { dataEvents ->
            handleDataEvents(dataEvents)
        }

        lifecycle.addObserver(wearObserver)
    }

    private fun handleDataEvents(dataEvents: DataEventBuffer) {
        // ... filter and process events ...
    }
}

Por qué es mejor:

  • Actividad más limpia: Quitas el código estándar de los métodos del ciclo de vida de la actividad.
  • Seguridad: DefaultLifecycleObserver ayuda a verificar que se quite el objeto de escucha, incluso si la actividad se destruye de forma inesperada, lo que evita pérdidas de memoria.
  • Reutilización: Puedes conectar este WearDataLayerObserver a cualquier actividad o elemento componible sin volver a escribir la lógica de registro.
  • Desacoplamiento: La lógica de cuándo escuchar se separa de la lógica de qué hacer con los datos.

Usa filtros con objetos de escucha activos

Como se mencionó anteriormente, del mismo modo en que puedes especificar filtros de intents para objetos WearableListenerServicebasados en manifiestos, también puedes usar filtros de intents cuando registras un objeto de escucha activo a través de la API de Wearable. Las mismas reglas se aplican a los objetos de escucha activos 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 con collectAsStateWithLifecycle(). Si implementas objetos de escucha de esta manera, tu app puede recibir eventos de forma más selectiva, lo que mejora su diseño y eficiencia.