Expande tu app a dispositivos wearable con la tecnología de Wear OS para mejorar la experiencia de salud y bienestar.
Cómo agregar un módulo de Wear OS
Android Studio proporciona un asistente práctico para agregar un módulo de Wear OS a tu app. En el menú File > New Module, selecciona Wear OS, como se muestra en la siguiente imagen:
![Asistente de módulos de Wear OS en Android Studio](https://developer.android.com/static/health-and-fitness/guides/basic-fitness-app/images/basic_fitness_app_img_8.png?authuser=7&hl=es-419)
Es importante tener en cuenta que el SDK mínimo debe ser el nivel de API 30 o superior para que puedas usar la versión más reciente de los Servicios de salud. Los Servicios de salud facilitan el seguimiento de métricas y el registro de datos mediante la configuración automática de sensores de salud.
Después de completar el asistente, sincroniza tu proyecto. Aparecerá la siguiente configuración de Run:
![Una imagen que muestra el botón de ejecución de la app de Wear OS](https://developer.android.com/static/health-and-fitness/guides/basic-fitness-app/images/basic_fitness_app_img_5.png?authuser=7&hl=es-419)
De esta manera, puedes ejecutar el módulo de Wear OS en un dispositivo wearable. Tienes dos opciones:
Ejecuta la app en un emulador.
Se ejecuta en un dispositivo real.
Cuando se ejecuta la configuración, se implementa la app en el emulador o dispositivo de Wear OS y se muestra la experiencia de "Hello World". 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 el módulo de Wear OS:
- Servicios de salud: Hace que acceder a los sensores y los datos en el reloj sea muy conveniente y ahorres energía.
- Hilt: Permite una inserción y administración eficaces de las dependencias.
Crea el administrador de Servicios de salud
Para que el uso de los Servicios de salud sea un poco más conveniente y expongas una API más pequeña y fluida, puedes crear un wrapper como este:
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 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 aún no lo hiciste, declara el permiso BODY_SENSORS
en el 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. En las versiones anteriores, puedes simular el flujo de datos del sensor. En una ventana de terminal, ejecuta este comando de ADB:
adb shell am broadcast \
-a "whs.USE_SYNTHETIC_PROVIDERS" \
com.google.android.wearable.healthservices
Si quieres ver diferentes valores de frecuencia cardíaca, prueba simular diferentes ejercicios. Este comando simula que caminas:
adb shell am broadcast \
-a "whs.synthetic.user.START_WALKING" \
com.google.android.wearable.healthservices
Este comando simula correr:
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
Leer datos de frec. cardíaca
Con el permiso BODY_SENSORS
otorgado, puedes leer la frecuencia cardíaca del usuario (heartRateMeasureFlow()
) en el HealthServicesManager
. En la IU de la app para Wear OS, aparece el valor actual de la frecuencia cardíaca que mide el sensor del dispositivo 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 vivo 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 la clase DataClient
en los Servicios de salud. En el siguiente fragmento de código, se muestra cómo enviar datos de frecuencia cardíaca que recopiló tu app 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")
}
}
}
Cómo recibir los datos en el teléfono
Para recibir los 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
}
}
}
}
}
Al completar este paso, observa algunos detalles interesantes:
- La anotación
@AndroidEntryPoint
nos permite usar Hilt en esta clase. @Inject lateinit var heartRateMonitor: HeartRateMonitor
insertará 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 los valores de frecuencia cardíaca recibidos 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 pone a disposición de los observadores de datos que usan 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 portátil
En la parte de tu app que se ejecuta en un dispositivo de mano, inserta el HeartRateMonitor
en el constructor de tu modelo de vista. Este objeto HeartRateMonitor
observa los datos de la frecuencia cardíaca y emite actualizaciones de la IU según sea necesario.