ادغام کردن واحد Wear OS

با گسترش برنامه سلامتی و تناسب اندام خود به دستگاه‌های پوشیدنی مجهز به Wear OS، تجربه کاربری آن را بهبود بخشید.

افزودن ماژول Wear OS

«استودیو Android» یک جادوگر مفید برای افزودن ماژول Wear OS به برنامه‌تان ارائه می‌دهد. در منو فایل > ماژول جدید، Wear OS را انتخاب کنید، همان‌طور که در تصویر زیر نشان داده شده است:

جادوگر ماژول Wear OS در «استودیو Android»
شکل ۱: ایجاد کردن واحد Wear OS

توجه داشته باشید که حداقل کیت توسعه نرم‌افزار باید میانای برنامه‌سازی کاربردی ۳۰ یا بالاتر باشد تا بتوانید از جدیدترین نسخه «خدمات سلامت» استفاده کنید. «خدمات سلامت» با پیکربندی خودکار حسگرهای سلامتی، ردیابی سنجه‌ها و ثبت داده‌ها را آسان‌تر می‌کند.

پس‌از تکمیل جادوگر، پروژه‌تان را همگام‌سازی کنید. پیکربندی اجرا زیر نشان داده می‌شود:

تصویری که دکمه اجرای برنامه Wear OS را نشان می‌دهد
شکل ۲: دکمه «اجرا» برای ماژول Wear OS جدید

این کار به شما امکان می‌دهد واحد Wear OS را در دستگاه پوشیدنی اجرا کنید. دو گزینه دارید:

اجرای پیکربندی، برنامه را در شبیه‌ساز یا دستگاه Wear OS مستقر می‌کند و تجربه «سلام دنیا» را نشان می‌دهد. این راه‌اندازی واسط کاربر پایه است که از Compose for Wear OS برای شروع کار با برنامه‌تان استفاده می‌کند.

افزودن Health Services و Hilt

کتابخانه‌های زیر را در ماژول Wear OS خود ادغام کنید:

  • Health Services: دسترسی به حسگرها و داده‌های ساعت را بسیار راحت‌تر و ازنظر مصرف انرژی کارآمدتر می‌کند.
  • Hilt: امکان تزریق و مدیریت مؤثر وابستگی را فراهم می‌کند.

ایجاد «مدیر خدمات بهداشتی»

برای اینکه استفاده از خدمات سلامتی کمی راحت‌تر شود و میانای برنامه‌سازی کاربردی کوچک‌تر و روان‌تری ارائه شود، می‌توانید پوششی مانند این ایجاد کنید:

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

پس‌از ایجاد کردن واحد Hilt برای مدیریت آن، از قطعه کد زیر استفاده کنید:

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

می‌توانید HealthServicesManager را مانند هر وابستگی Hilt دیگری تزریق کنید.

HealthServicesManager جدید روش heartRateMeasureFlow() را ارائه می‌دهد که شنونده‌ای را برای پایشگر قلب ثبت می‌کند و داده‌های دریافتی را منتشر می‌کند.

فعال کردن به‌روزرسانی‌های داده در دستگاه‌های پوشیدنی

به‌روزرسانی‌های داده‌های مرتبط با تناسب اندام به اجازه BODY_SENSORS نیاز دارد. اگر قبلاً این کار را نکرده‌اید، اجازه BODY_SENSORS را در فایل مانیفست برنامه‌تان اعلام کنید. سپس، همان‌طور که در این قطعه کد نشان داده شده است، اجازه را درخواست کنید:

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

[...]

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

اگر برنامه‌تان را در دستگاه فیزیکی آزمایش کنید، داده‌ها باید شروع به به‌روزرسانی کنند.

از Wear OS 4، شبیه‌سازها داده‌های آزمایشی را نیز به‌طور خودکار نشان می‌دهند. در نسخه‌های قبلی، می‌توانید جریان داده را از حسگر شبیه‌سازی کنید. در پنجره ترمینال، این فرمان ADB را اجرا کنید:

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

برای دیدن مقادیر مختلف ضربان قلب، تمرین‌های مختلف را شبیه‌سازی کنید. این فرمان راه رفتن را شبیه‌سازی می‌کند:

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

این فرمان اجرای موارد زیر را شبیه‌سازی می‌کند:

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

برای توقف شبیه‌سازی داده‌ها، این فرمان را اجرا کنید:

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

خواندن داده‌های ضربان قلب

با اعطای اجازه BODY_SENSORS، می‌توانید ضربان قلب کاربر (heartRateMeasureFlow()) را در HealthServicesManager بخوانید. در واسط کاربر برنامه Wear OS، مقدار ضربان قلب فعلی که توسط حسگر دستگاه پوشیدنی اندازه‌گیری می‌شود نشان داده می‌شود.

در ViewModel، جمع‌آوری داده را بااستفاده از شیء جریان ضربان قلب شروع کنید، همان‌طور که در قطعه کد زیر نشان داده شده است:

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

از شیء ترکیب‌پذیری مشابه زیر برای نمایش داده‌های زنده در رابط کاربری برنامه‌تان استفاده کنید:

val heartRate by viewModel.hr

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

ارسال داده به دستگاه دستی

برای ارسال داده‌های سلامتی و تناسب اندام به دستگاه دستی، از کلاس DataClient در «خدمات سلامت» استفاده کنید. تکه‌کد زیر نحوه ارسال داده‌های ضربان قلب را که برنامه‌تان قبلاً جمع‌آوری کرده است نشان می‌دهد:

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

دریافت داده‌ها در تلفن

برای دریافت داده‌ها در تلفن، 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
                }
            }
        }
    }
}

پس‌از تکمیل این مرحله، به چند جزئیات جالب توجه کنید:

  • حاشیه‌نویسی @AndroidEntryPoint به ما امکان می‌دهد از Hilt در این کلاس استفاده کنیم
  • ‫@Inject lateinit var heartRateMonitor: HeartRateMonitor درواقع وابستگی را در این کلاس تزریق می‌کند
  • کلاس onDataChanged() را پیاده‌سازی می‌کند و مجموعه‌ای از رویدادها را دریافت می‌کند که می‌توانید تجزیه و استفاده کنید

منطق HeartRateMonitor زیر به شما امکان می‌دهد مقادیر ضربان قلب دریافتی را به بخش دیگری از پایگاه کد برنامه‌تان ارسال کنید:

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

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

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

گذرگاه داده رویدادها را از روش onDataChanged() دریافت می‌کند و آن‌ها را بااستفاده از SharedFlow دردسترس ناظران داده قرار می‌دهد.

بخش نهایی، اعلام Service در برنامه تلفن است 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>

نمایش داده‌های هم‌زمان در دستگاه دستی

در بخشی از برنامه‌تان که در دستگاه دستی اجرا می‌شود، HeartRateMonitor را به سازنده مدل نمای خود تزریق کنید. این شیء HeartRateMonitor داده‌های ضربان قلب را مشاهده می‌کند و به‌روزرسانی‌های میانای کاربری را درصورت نیاز منتشر می‌کند.