Améliorez l'expérience de santé et de remise en forme de votre application en l'étendant aux appareils connectés fonctionnant sous Wear OS.
Ajouter un module Wear OS
Android Studio fournit un assistant pratique pour ajouter un module Wear OS à votre application. Dans le menu File > New Module (Fichier > Nouveau module), sélectionnez Wear OS, comme illustré dans l'image suivante :
Il est important de noter que le SDK minimal doit être API 30 ou version ultérieure pour vous permettre d'utiliser la dernière version de Health Services. Les Services Santé facilitent le suivi des métriques et l'enregistrement des données en configurant automatiquement les capteurs de santé.
Une fois l'assistant terminé, synchronisez votre projet. La configuration Run (Exécuter) suivante s'affiche :
Cela vous permet d'exécuter le module Wear OS sur un appareil wearable. Deux possibilités s'offrent à vous :
Exécutez l'application sur un émulateur.
Exécutez l'application sur un appareil réel.
L'exécution de la configuration déploie l'application sur l'émulateur ou l'appareil Wear OS et affiche une expérience "Hello World". Il s'agit de la configuration de base de l'UI, à l'aide de Compose pour Wear OS, pour commencer à utiliser votre application.
Ajouter des services de santé et Hilt
Intégrez les bibliothèques suivantes à votre module Wear OS :
- Services de santé : ils permettent d'accéder aux capteurs et aux données de la montre de manière très pratique et plus économe en énergie.
- Hilt : permet une injection et une gestion efficaces des dépendances.
Créer le gestionnaire des Services Santé
Pour faciliter l'utilisation de Services Santé et exposer une API plus petite et plus fluide, vous pouvez créer un wrapper comme celui-ci :
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()
}
Une fois le module Hilt créé pour le gérer, utilisez l'extrait de code suivant :
@Module
@InstallIn(SingletonComponent::class)
internal object DataModule {
@Provides
@Singleton
fun provideHealthServices(@ApplicationContext context: Context): HealthServicesManager = HealthServicesManager(context)
}
Vous pouvez injecter HealthServicesManager comme n'importe quelle autre dépendance Hilt.
Le nouveau HealthServicesManager fournit une méthode heartRateMeasureFlow() qui enregistre un écouteur pour le cardiofréquencemètre et émet les données reçues.
Activer les mises à jour de données sur les appareils connectés
Les mises à jour des données liées à la forme physique nécessitent l'autorisation BODY_SENSORS. Si vous ne l'avez pas déjà fait, déclarez l'autorisation BODY_SENSORS dans le fichier manifeste de votre application. Demandez ensuite l'autorisation, comme indiqué dans cet extrait :
val permissionState = rememberPermissionState(
permission = Manifest.permission.BODY_SENSORS,
onPermissionResult = { granted -> /* do something */ }
)
[...]
if (permissionState.status.isGranted) {
// do something
} else {
permissionState.launchPermissionRequest()
}
Si vous testez votre application sur un appareil physique, les données devraient commencer à se mettre à jour.
Depuis Wear OS 4, les émulateurs affichent également automatiquement les données de test. Dans les versions précédentes, vous pouvez simuler le flux de données du capteur. Dans une fenêtre de terminal, exécutez cette commande ADB :
adb shell am broadcast \
-a "whs.USE_SYNTHETIC_PROVIDERS" \
com.google.android.wearable.healthservices
Pour afficher différentes valeurs de fréquence cardiaque, essayez de simuler différents exercices. Cette commande simule une marche :
adb shell am broadcast \
-a "whs.synthetic.user.START_WALKING" \
com.google.android.wearable.healthservices
Cette commande simule une course :
adb shell am broadcast \
-a "whs.synthetic.user.START_RUNNING" \
com.google.android.wearable.healthservices
Pour arrêter la simulation des données, exécutez la commande suivante :
adb shell am broadcast -a \
"whs.USE_SENSOR_PROVIDERS" \
com.google.android.wearable.healthservices
Lire les données de fréquence cardiaque
Une fois l'autorisation BODY_SENSORS accordée, vous pouvez lire la fréquence cardiaque de l'utilisateur (heartRateMeasureFlow()) dans HealthServicesManager. Dans l'UI de l'application Wear OS, la valeur de la fréquence cardiaque actuelle s'affiche, mesurée par le capteur de l'appareil wearable.
Dans votre ViewModel, commencez à collecter des données à l'aide de l'objet de flux de fréquence cardiaque, comme indiqué dans l'extrait suivant :
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
}
}
Utilisez un objet composable semblable à ce qui suit pour afficher les données en direct dans l'UI de votre application :
val heartRate by viewModel.hr
Text(
text = "Heart Rate: $heartRate",
style = MaterialTheme.typography.display1
)
Envoyer des données vers un appareil portable
Pour envoyer des données de santé et de remise en forme à un appareil mobile, utilisez la classe DataClient dans Services Santé. L'extrait de code suivant montre comment envoyer les données de fréquence cardiaque que votre application a collectées précédemment :
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")
}
}
}
Recevoir les données sur le téléphone
Pour recevoir les données sur le téléphone, créez 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
}
}
}
}
}
Une fois cette étape terminée, notez quelques détails intéressants :
- L'annotation
@AndroidEntryPointnous permet d'utiliser Hilt dans cette classe. @Inject lateinit var heartRateMonitor: HeartRateMonitorinjectera effectivement une dépendance dans cette classe.- La classe implémente
onDataChanged()et reçoit une collection d'événements que vous pouvez analyser et utiliser.
La logique HeartRateMonitor suivante vous permet d'envoyer les valeurs de fréquence cardiaque reçues à une autre partie du code de votre application :
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 données reçoit les événements de la méthode onDataChanged() et les met à la disposition des observateurs de données à l'aide d'un SharedFlow.
La dernière partie est la déclaration de Service dans l'application mobile : 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>
Afficher des données en temps réel sur un appareil mobile
Dans la partie de votre application qui s'exécute sur un appareil mobile, injectez HeartRateMonitor dans le constructeur de votre modèle de vue. Cet objet HeartRateMonitor observe les données de fréquence cardiaque et émet des mises à jour de l'UI si nécessaire.