Intégrer un module Wear OS

Améliorez l'expérience de santé et de remise en forme de votre application en l'étendant aux accessoires connectés appareils équipés de Wear OS.

Ajouter un module Wear OS

Android Studio fournit un assistant pratique pour ajouter un module Wear OS à votre application. Dans l'option Fichier > Nouveau module, sélectionnez Wear OS, comme illustré ci-dessous image:

<ph type="x-smartling-placeholder">
</ph> Assistant du module Wear OS dans Android Studio <ph type="x-smartling-placeholder">
</ph> Figure 1: Créer un module Wear OS

Il est important de noter que le Minimum SDK (SDK minimal) doit correspondre à l'API 30 ou à une version ultérieure. pour vous permettre d'utiliser la dernière version des Services Santé. Services Santé facilite le suivi des métriques et l'enregistrement des données en configurant capteurs automatiquement.

Une fois les étapes de l'assistant terminées, synchronisez votre projet. L'exécution suivante configuration s'affiche:

<ph type="x-smartling-placeholder">
</ph> Image montrant le bouton d&#39;exécution de l&#39;application Wear OS <ph type="x-smartling-placeholder">
</ph> Figure 2: Bouton d'exécution du nouveau module Wear OS

Cela vous permet d'exécuter le module Wear OS sur un accessoire connecté. Deux possibilités s'offrent à vous :

L'exécution de la configuration déploie l'application sur l'émulateur Wear OS ou et affiche un "hello world" expérience. Il s'agit de la configuration de base de l'interface utilisateur, Compose pour Wear OS afin de commencer à utiliser votre application.

Ajouter Services Santé et Hilt

Intégrez les bibliothèques suivantes à votre module Wear OS:

  • Services Santé:permet d'accéder aux capteurs et aux données de la montre très pratique et é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 un fichier et plus fluide, vous pouvez créer un wrapper comme ceci:

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 que vous avez créé le module Hilt 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 de Hilt.

Le nouveau HealthServicesManager fournit une méthode heartRateMeasureFlow() qui enregistre un écouteur pour le moniteur cardiaque et émet les données reçues.

Activer la mise à jour des données sur les wearables

Les mises à jour de données liées à l'activité physique nécessitent l'autorisation BODY_SENSORS. Si vous ne l'avez pas déjà fait, déclarez l'autorisation BODY_SENSORS dans votre le fichier manifeste de l'application. Ensuite, demandez l'autorisation, comme indiqué dans cet extrait de code:

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, la mise à jour des données devrait commencer.

À partir de Wear OS 4, les émulateurs affichent également automatiquement les données de test. Lors de la précédente vous pouvez simuler le flux de données du capteur. Dans un terminal exécutez cette commande ADB:

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

Pour voir différentes valeurs de fréquence cardiaque, essayez de simuler différents exercices. Cette commande simule la marche:

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

Cette commande simule l'exécution:

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

Pour arrêter de simuler les 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

Avec l'autorisation BODY_SENSORS accordée, vous pouvez lire la fréquence cardiaque de l'utilisateur (heartRateMeasureFlow()) dans le HealthServicesManager. Dans l'application Wear OS, UI, la fréquence cardiaque actuelle est affichée, mesurée par le capteur sur le accessoire connecté.

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 actives dans l'interface utilisateur 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 vers un appareil portable, utilisez le DataClient dans Services Santé. L'extrait de code suivant montre comment envoyer un cœur Évaluez les données précédemment collectées par votre application:

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

À la fin de cette étape, notez quelques détails intéressants:

  • L'annotation @AndroidEntryPoint permet d'utiliser Hilt dans cette classe.
  • Le @Inject lateinit var heartRateMonitor: HeartRateMonitor sera en effet injecter une dépendance dans cette classe
  • La classe implémente onDataChanged() et reçoit un ensemble d'événements qui vous pouvez analyser et utiliser

La logique HeartRateMonitor suivante vous permet d'envoyer la fréquence cardiaque reçue à une autre partie du codebase 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 transmet disponibles pour les observateurs de données à l'aide d'un SharedFlow.

Le dernier élément est la déclaration de Service dans l'application Téléphone. 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 les données en temps réel sur un appareil portable

Dans la partie de votre application qui s'exécute sur un appareil portable, injectez le HeartRateMonitor dans le constructeur de votre modèle de vue. Ce/Cet/Cette HeartRateMonitor observe les données de fréquence cardiaque et émet des mises à jour de l'interface utilisateur si nécessaire.