Mengintegrasikan modul Wear OS

Tingkatkan pengalaman kesehatan dan kebugaran aplikasi Anda dengan memperluasnya ke perangkat wearable yang didukung oleh Wear OS.

Menambahkan modul Wear OS

Android Studio menyediakan wizard praktis untuk menambahkan modul Wear OS ke aplikasi Anda. Di menu File > Modul Baru, pilih Wear OS, seperti yang ditunjukkan pada gambar berikut:

Wizard modul Wear OS di Android Studio
Gambar 1: Membuat modul Wear OS

Penting untuk diperhatikan bahwa Minimum SDK harus API 30 atau yang lebih tinggi agar Anda dapat menggunakan Fitur Kesehatan versi terbaru. Fitur Kesehatan memudahkan pemantauan metrik dan pencatatan data dengan mengonfigurasi sensor kesehatan secara otomatis.

Setelah menyelesaikan wizard tersebut, sinkronkan project Anda. Konfigurasi Run berikut akan muncul:

Gambar yang menunjukkan tombol jalankan aplikasi Wear OS
Gambar 2: Tombol Jalankan untuk modul Wear OS baru

Tindakan ini memungkinkan Anda menjalankan modul Wear OS di perangkat wearable. Anda memiliki 2 opsi:

Menjalankan konfigurasi akan men-deploy aplikasi ke emulator atau perangkat Wear OS dan menampilkan pengalaman "hello world". Ini adalah penyiapan UI dasar, yang menggunakan Compose untuk Wear OS, untuk memulai aplikasi Anda.

Menambahkan Fitur Kesehatan dan Hilt

Integrasikan library berikut ke dalam modul Wear OS Anda:

  • Fitur Kesehatan: membuat akses sensor dan data di smartwatch sangat praktis dan lebih hemat daya.
  • Hilt: Memungkinkan injeksi dan pengelolaan dependensi yang efektif.

Membuat Pengelola Fitur Kesehatan

Agar penggunaan Fitur Kesehatan sedikit lebih nyaman, dan mengekspos API yang lebih kecil dan lebih lancar, Anda dapat membuat wrapper seperti ini:

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

Setelah Anda membuat modul Hilt untuk mengelolanya, gunakan cuplikan berikut:

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

Anda dapat memasukkan HealthServicesManager sebagai dependensi Hilt lainnya.

HealthServicesManager baru menyediakan metode heartRateMeasureFlow() yang mendaftarkan pemroses untuk pemantau jantung dan memunculkan data yang diterima.

Mengaktifkan pembaruan data di perangkat wearable

Pembaruan data terkait kebugaran memerlukan izin BODY_SENSORS. Jika belum melakukannya, deklarasikan izin BODY_SENSORS di file manifes aplikasi Anda. Kemudian, minta izin, seperti yang ditunjukkan dalam cuplikan ini:

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

[...]

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

Jika Anda menguji aplikasi di perangkat fisik, data akan mulai diupdate.

Mulai Wear OS 4, emulator juga menampilkan data pengujian secara otomatis. Pada versi sebelumnya, Anda dapat menyimulasikan aliran data dari sensor. Di jendela terminal, jalankan perintah ADB ini:

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

Untuk melihat nilai detak jantung yang berbeda, coba simulasi latihan yang berbeda. Perintah ini menyimulasikan berjalan:

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

Perintah ini menyimulasikan berjalannya:

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

Untuk berhenti menyimulasikan data, jalankan perintah ini:

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

Membaca data detak jantung

Dengan izin BODY_SENSORS yang diberikan, Anda dapat membaca detak jantung pengguna (heartRateMeasureFlow()) di HealthServicesManager. Di UI aplikasi Wear OS, nilai detak jantung saat ini akan muncul, yang diukur oleh sensor di perangkat wearable.

Di ViewModel, mulai kumpulkan data menggunakan objek alur detak jantung, seperti yang ditunjukkan dalam cuplikan berikut:

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

Gunakan objek composable yang mirip dengan yang berikut ini untuk menampilkan data langsung di UI aplikasi Anda:

val heartRate by viewModel.hr

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

Mengirim data ke perangkat genggam

Untuk mengirim data kesehatan dan kebugaran ke perangkat genggam, gunakan class DataClient di Fitur Kesehatan. Cuplikan kode berikut menunjukkan cara mengirim data detak jantung yang sebelumnya dikumpulkan aplikasi Anda:

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

Terima data di ponsel

Untuk menerima data di ponsel, buat 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
                }
            }
        }
    }
}

Setelah menyelesaikan langkah ini, perhatikan beberapa detail menarik:

  • Anotasi @AndroidEntryPoint memungkinkan kita menggunakan Hilt di class ini
  • @Inject lateinit var heartRateMonitor: HeartRateMonitor memang akan memasukkan dependensi dalam class ini
  • Class ini menerapkan onDataChanged() dan menerima sekumpulan peristiwa yang dapat Anda urai dan gunakan

Logika HeartRateMonitor berikut memungkinkan Anda mengirim nilai detak jantung yang diterima ke bagian lain codebase aplikasi:

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

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

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

Bus data menerima peristiwa dari metode onDataChanged() dan menyediakannya untuk observer data menggunakan SharedFlow.

Bit terakhir adalah deklarasi Service di aplikasi ponsel 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>

Menampilkan data real-time di perangkat genggam

Di bagian aplikasi yang berjalan di perangkat genggam, masukkan HeartRateMonitor ke dalam konstruktor model tampilan Anda. Objek HeartRateMonitor ini mengamati data detak jantung dan memunculkan update UI sesuai kebutuhan.