یک ماژول Wear OS را ادغام کنید

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

اضافه کردن ماژول Wear OS

اندروید استودیو یک ویزارد مفید برای افزودن ماژول Wear OS به برنامه شما ارائه می‌دهد. در منوی File > New Module ، همانطور که در تصویر زیر نشان داده شده است، Wear OS را انتخاب کنید:

ویزارد ماژول Wear OS در اندروید استودیو
شکل ۱ : ایجاد یک ماژول Wear OS

لازم به ذکر است که حداقل SDK باید API 30 یا بالاتر باشد تا بتوانید از آخرین نسخه Health Services استفاده کنید. Health Services با پیکربندی خودکار حسگرهای سلامت، ردیابی معیارها و ثبت داده‌ها را آسان‌تر می‌کند.

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

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

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

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

خدمات درمانی و Hilt را اضافه کنید

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

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

مدیر خدمات درمانی را ایجاد کنید

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

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 در Health Services استفاده کنید. قطعه کد زیر نحوه ارسال داده‌های ضربان قلب که برنامه شما قبلاً جمع‌آوری کرده است را نشان می‌دهد:

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 داده‌های ضربان قلب را مشاهده می‌کند و در صورت نیاز، به‌روزرسانی‌های رابط کاربری را منتشر می‌کند.