Cómo integrar un módulo de Wear OS

Expande tu app a wearable para mejorar la experiencia de salud y fitness dispositivos con tecnología Wear OS

Cómo agregar un módulo de Wear OS

Android Studio proporciona un asistente útil para agregar un módulo de Wear OS a tu app. En la pestaña Archivo > En el menú New Module, selecciona Wear OS, como se muestra a continuación. imagen:

Asistente de módulos de Wear OS en Android Studio
Figura 1: Cómo crear un módulo de Wear OS
.

Es importante tener en cuenta que el Minimum SDK debe ser API 30 o superior. para permitirle usar la versión más reciente de los Servicios de salud. Servicios de salud facilita el seguimiento de las métricas y el registro de datos mediante la configuración del estado automáticamente.

Después de completar los pasos del asistente, sincroniza tu proyecto. Con el siguiente comando Run, configuración de Terraform:

Una imagen que muestra el botón de ejecución de la app para Wear OS
Figura 2: Botón Run para el nuevo módulo de Wear OS
.

De esta manera, podrás ejecutar el módulo de Wear OS en un dispositivo wearable. Tienes dos opciones:

Cuando se ejecuta la configuración, se implementa la app en el emulador de Wear OS. y se muestra el mensaje “Hello World” una experiencia fluida a los desarrolladores. Esta es la configuración básica de la IU, con Compose para Wear OS, para comenzar a usar tu app.

Cómo agregar servicios de salud y Hilt

Integra las siguientes bibliotecas en tu módulo de Wear OS:

  • Servicios de salud: Facilita el acceso a los sensores y los datos del reloj. muy conveniente y eficiente en términos de energía.
  • Hilt: Permite una inserción y administración eficaces de dependencias.

Cómo crear el Administrador de servicios de salud

Para que el uso de los Servicios de salud sea un poco más conveniente y expongas una imagen y más fluida, puedes crear un wrapper como el siguiente:

private const val TAG = "WATCHMAIN"

class HealthServicesManager(context: Context) {
    private val measureClient = HealthServices.getClient(context).measureClient

    suspend fun hasHeartRateCapability() = runCatching {
        val capabilities = measureClient.getCapabilities()
        (DataType.HEART_RATE_BPM in capabilities.supportedDataTypesMeasure)
    }.getOrDefault(false)

    /**
     * Returns a cold flow. When activated, the flow will register a callback for heart rate data
     * and start to emit messages. When the consuming coroutine is canceled, the measure callback
     * is unregistered.
     *
     * [callbackFlow] creates a  bridge between a callback-based API and Kotlin flows.
     */
    @ExperimentalCoroutinesApi
    fun heartRateMeasureFlow(): Flow<MeasureMessage> = callbackFlow {
        val callback = object : MeasureCallback {
            override fun onAvailabilityChanged(dataType: DeltaDataType<*, *>, availability: Availability) {
                // Only send back DataTypeAvailability (not LocationAvailability)
                if (availability is DataTypeAvailability) {
                    trySendBlocking(MeasureMessage.MeasureAvailability(availability))
                }
            }

            override fun onDataReceived(data: DataPointContainer) {
                val heartRateBpm = data.getData(DataType.HEART_RATE_BPM)
                Log.d(TAG, "💓 Received heart rate: ${heartRateBpm.first().value}")
                trySendBlocking(MeasureMessage.MeasureData(heartRateBpm))
            }
        }

        Log.d(TAG, "⌛ Registering for data...")
        measureClient.registerMeasureCallback(DataType.HEART_RATE_BPM, callback)

        awaitClose {
            Log.d(TAG, "👋 Unregistering for data")
            runBlocking {
                measureClient.unregisterMeasureCallback(DataType.HEART_RATE_BPM, callback)
            }
        }
    }
}

sealed class MeasureMessage {
    class MeasureAvailability(val availability: DataTypeAvailability) : MeasureMessage()
    class MeasureData(val data: List<SampleDataPoint<Double>>) : MeasureMessage()
}

Una vez que hayas creado el módulo de Hilt para administrarlo, usa el siguiente fragmento:

@Module
@InstallIn(SingletonComponent::class)
internal object DataModule {
    @Provides
    @Singleton
    fun provideHealthServices(@ApplicationContext context: Context): HealthServicesManager = HealthServicesManager(context)
}

puedes insertar el HealthServicesManager como cualquier otra dependencia de Hilt.

El nuevo HealthServicesManager proporciona un método heartRateMeasureFlow() que registra un objeto de escucha para el monitor cardíaco y emite los datos recibidos.

Cómo habilitar las actualizaciones de datos en dispositivos wearable

Las actualizaciones de datos relacionados con el entrenamiento requieren el permiso BODY_SENSORS. Si si aún no lo has hecho, declara el permiso BODY_SENSORS en tu archivo de manifiesto de tu app. Luego, solicita el permiso, como se muestra en este fragmento:

val permissionState = rememberPermissionState(
    permission = Manifest.permission.BODY_SENSORS,
    onPermissionResult = { granted -> /* do something */ }
)

[...]

if (permissionState.status.isGranted) {
    // do something
} else {
    permissionState.launchPermissionRequest()
}

Si pruebas la app en un dispositivo físico, los datos deberían comenzar a actualizarse.

A partir de Wear OS 4, los emuladores también muestran datos de prueba automáticamente. El anterior puedes simular el flujo de datos desde el sensor. En una terminal ejecuta este comando de ADB:

adb shell am broadcast \
-a "whs.USE_SYNTHETIC_PROVIDERS" \
com.google.android.wearable.healthservices

Para ver diferentes valores de frecuencia cardíaca, intenta simular distintos ejercicios. Este comando simula el recorrido a pie:

adb shell am broadcast \
-a "whs.synthetic.user.START_WALKING" \
com.google.android.wearable.healthservices

Este comando simula la ejecución de los siguientes elementos:

adb shell am broadcast \
-a "whs.synthetic.user.START_RUNNING" \
com.google.android.wearable.healthservices

Para dejar de simular los datos, ejecuta este comando:

adb shell am broadcast -a \
"whs.USE_SENSOR_PROVIDERS" \
com.google.android.wearable.healthservices

Cómo leer datos de frecuencia cardíaca

Si otorgas el permiso BODY_SENSORS, podrás leer la frecuencia cardíaca del usuario (heartRateMeasureFlow()) en HealthServicesManager. En la app de Wear OS IU, aparece el valor actual de la frecuencia cardíaca, medido por el sensor del wearable.

En tu ViewModel, comienza a recopilar datos con el objeto de flujo de frecuencia cardíaca. como se muestra en el siguiente fragmento:

val hr: MutableState<Double> = mutableStateOf(0.0)

[...]

healthServicesManager
    .heartRateMeasureFlow()
    .takeWhile { enabled.value }
    .collect { measureMessage ->
        when (measureMessage) {
            is MeasureData -> {
                val latestHeartRateValue = measureMessage.data.last().value
                hr.value = latestHeartRateValue
            }

            is MeasureAvailability -> availability.value =
                    measureMessage.availability
        }
    }

Usa un objeto componible similar al siguiente para mostrar los datos en tiempo real en la IU de tu app:

val heartRate by viewModel.hr

Text(
  text = "Heart Rate: $heartRate",
  style = MaterialTheme.typography.display1
)

Cómo enviar datos a un dispositivo de mano

Para enviar datos de salud y actividad física a un dispositivo de mano, usa DataClient. en Servicios de salud. En el siguiente fragmento de código, se muestra cómo enviar mensajes de corazón datos de tarifas que la app recopiló anteriormente:

class HealthServicesManager(context: Context) {
    private val dataClient by lazy { Wearable.getDataClient(context) }

[...]

    suspend fun sendToHandheldDevice(heartRate: Int) {
        try {
            val result = dataClient
                .putDataItem(PutDataMapRequest
                    .create("/heartrate")
                    .apply { dataMap.putInt("heartrate", heartRate) }
                    .asPutDataRequest()
                    .setUrgent())
                .await()

            Log.d(TAG, "DataItem saved: $result")
        } catch (cancellationException: CancellationException) {
            throw cancellationException
        } catch (exception: Exception) {
            Log.d(TAG, "Saving DataItem failed: $exception")
        }
    }
}

Recibir los datos en el teléfono

Para recibir datos en el teléfono, crea un WearableListenerService:

@AndroidEntryPoint
class DataLayerListenerService : WearableListenerService() {

    @Inject
    lateinit var heartRateMonitor: HeartRateMonitor

    override fun onDataChanged(dataEvents: DataEventBuffer) {

        dataEvents.forEach { event ->
            when (event.type) {
                DataEvent.TYPE_CHANGED -> {
                    event.dataItem.run {
                        if (uri.path?.compareTo("/heartrate") == 0) {
                            val heartRate = DataMapItem.fromDataItem(this)
                                    .dataMap.getInt(HR_KEY)
                            Log.d("DataLayerListenerService",
                                    "New heart rate value received: $heartRate")
                            heartRateMonitor.send(heartRate)
                        }
                    }
                }

                DataEvent.TYPE_DELETED -> {
                    // DataItem deleted
                }
            }
        }
    }
}

Cuando completes este paso, observa algunos detalles interesantes:

  • La anotación @AndroidEntryPoint nos permite usar Hilt en esta clase.
  • @Inject lateinit var heartRateMonitor: HeartRateMonitor, de hecho, Inserta una dependencia en esta clase.
  • La clase implementa onDataChanged() y recibe una colección de eventos que puedes analizar y usar

La siguiente lógica de HeartRateMonitor te permite enviar la frecuencia cardíaca recibida de salida a otra parte de la base de código de tu app:

class HeartRateMonitor {
    private val datapoints = MutableSharedFlow<Int>(extraBufferCapacity = 10)

    fun receive(): SharedFlow<Int> = datapoints.asSharedFlow()

    fun send(hr: Int) {
        datapoints.tryEmit(hr)
    }
}

Un bus de datos recibe los eventos del método onDataChanged() y los hace. disponible para los observadores de datos usando un SharedFlow.

El último bit es la declaración de Service en la aplicación para teléfonos. AndroidManifest.xml

<service
    android:name=".DataLayerListenerService"
    android:exported="true">
    <intent-filter>
        <!-- listeners receive events that match the action and data filters -->
        <action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
        <data
            android:host="*"
            android:pathPrefix="/heartrate"
            android:scheme="wear" />
    </intent-filter>
</service>

Cómo mostrar datos en tiempo real en un dispositivo de mano

En la parte de la app que se ejecuta en un dispositivo de mano, inserta el HeartRateMonitor en el constructor de tu modelo de vista. Este/a HeartRateMonitor observa los datos de la frecuencia cardíaca y emite actualizaciones de la IU según sea necesario.