Integrare un modulo Wear OS

Migliora l'esperienza di salute e fitness della tua app estendendola ai dispositivi indossabili con Wear OS.

Aggiungere un modulo Wear OS

Android Studio fornisce una procedura guidata pratica per aggiungere un modulo Wear OS alla tua app. Nel menu File > Nuovo modulo, seleziona Wear OS, come mostrato nell'immagine seguente:

Procedura guidata per i moduli Wear OS in Android Studio
Figura 1: crea un modulo Wear OS

È importante notare che l'SDK minimo deve essere API 30 o superiore per consentirti di utilizzare l'ultima versione di Health Services. Servizi per la salute semplifica il monitoraggio delle metriche e la registrazione dei dati configurando automaticamente i sensori per la salute.

Dopo aver completato la procedura guidata, sincronizza il progetto. Viene visualizzata la seguente configurazione Esegui:

Un'immagine che mostra il pulsante di esecuzione dell'app Wear OS
Figura 2: pulsante Esegui per il nuovo modulo Wear OS

In questo modo puoi eseguire il modulo Wear OS su un dispositivo indossabile. Sono disponibili due opzioni:

L'esecuzione della configurazione esegue il deployment dell'app nell'emulatore o nel dispositivo Wear OS e mostra un'esperienza "Hello world". Questa è la configurazione di base dell'interfaccia utente, che utilizza Compose for Wear OS, per iniziare a utilizzare la tua app.

Aggiungere servizi sanitari e Hilt

Integra le seguenti librerie nel modulo Wear OS:

  • Servizi per la salute: rendono l'accesso ai sensori e ai dati sullo smartwatch molto comodo e più efficiente dal punto di vista energetico.
  • Hilt: consente un'iniezione e una gestione delle dipendenze efficaci.

Crea Health Services Manager

Per semplificare l'utilizzo di Health Services ed esporre un'API più piccola e fluida, puoi creare un wrapper come questo:

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 volta creato il modulo Hilt per gestirlo, utilizza il seguente snippet:

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

puoi inserire HealthServicesManager come qualsiasi altra dipendenza Hilt.

Il nuovo HealthServicesManager fornisce un metodo heartRateMeasureFlow() che registra un listener per il monitor cardiaco ed emette i dati ricevuti.

Attivare gli aggiornamenti dei dati sui dispositivi indossabili

Gli aggiornamenti dei dati relativi all'attività fisica richiedono l'autorizzazione BODY_SENSORS. Se non l'hai ancora fatto, dichiara l'autorizzazione BODY_SENSORS nel file manifest della tua app. Dopodiché, richiedi l'autorizzazione, come mostrato in questo snippet:

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

[...]

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

Se testi l'app su un dispositivo fisico, i dati dovrebbero iniziare ad aggiornarsi.

A partire da Wear OS 4, gli emulatori mostrano automaticamente anche i dati di test. Nelle versioni precedenti, puoi simulare il flusso di dati dal sensore. In una finestra del terminale, esegui questo comando ADB:

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

Per visualizzare valori diversi del battito cardiaco, prova a simulare esercizi diversi. Questo comando simula la camminata:

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

Questo comando simula l'esecuzione di:

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

Per interrompere la simulazione dei dati, esegui questo comando:

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

Lettura dei dati sul battito cardiaco

Con l'autorizzazione BODY_SENSORS concessa, puoi leggere la frequenza cardiaca dell'utente (heartRateMeasureFlow()) in HealthServicesManager. Nell'interfaccia utente dell'app Wear OS, viene visualizzato il valore attuale del battito cardiaco, misurato dal sensore sul dispositivo indossabile.

Nel tuo ViewModel, inizia a raccogliere dati utilizzando l'oggetto flusso della frequenza cardiaca, come mostrato nel seguente snippet:

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

Utilizza un oggetto componibile simile al seguente per visualizzare i dati in tempo reale nell'interfaccia utente della tua app:

val heartRate by viewModel.hr

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

Inviare dati a un dispositivo portatile

Per inviare dati su salute e attività fisica a un dispositivo portatile, utilizza la classe DataClient in Health Services. Il seguente snippet di codice mostra come inviare i dati della frequenza cardiaca raccolti in precedenza dalla tua app:

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

Ricevere i dati sullo smartphone

Per ricevere i dati sullo smartphone, 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 termine di questo passaggio, nota alcuni dettagli interessanti:

  • L'annotazione @AndroidEntryPoint ci consente di utilizzare Hilt in questa classe
  • @Inject lateinit var heartRateMonitor: HeartRateMonitor inserirà una dipendenza in questa classe
  • La classe implementa onDataChanged() e riceve una raccolta di eventi che puoi analizzare e utilizzare

La seguente logica HeartRateMonitor ti consente di inviare i valori della frequenza cardiaca ricevuti a un'altra parte del codebase della tua 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 di dati riceve gli eventi dal metodo onDataChanged() e li rende disponibili agli osservatori di dati utilizzando un SharedFlow.

L'ultimo passaggio è la dichiarazione del Service nell'applicazione per smartphone 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>

Mostrare i dati in tempo reale su un dispositivo portatile

Nella parte dell'app che viene eseguita su un dispositivo portatile, inserisci HeartRateMonitor nel costruttore del modello di visualizzazione. Questo oggetto HeartRateMonitor osserva i dati della frequenza cardiaca ed emette aggiornamenti dell'interfaccia utente in base alle esigenze.