طرح‌های تمرین

‫Health Connect نوع داده تمرین برنامه‌ریزی‌شده را ارائه می‌دهد تا برنامه‌های تمرینی بتوانند برنامه‌های تمرینی بنویسند و برنامه‌های تمرین بتوانند برنامه‌های تمرینی را بخوانند. تمرین‌های ضبط‌شده (تمرین‌ها) را می‌توان برای تجزیه‌وتحلیل عملکرد شخصی‌سازی‌شده بازخوانی کرد تا به کاربران کمک شود به اهداف تمرینی خود دست یابند.

بررسی دردسترس بودن Health Connect

برنامه شما باید قبل‌از تلاش برای استفاده از Health Connect، بررسی کند که آیا Health Connect در دستگاه کاربر دردسترس است یا نه. ‫Health Connect ممکن است در همه دستگاه‌ها پیش‌نصب نشده باشد یا ممکن است غیرفعال باشد. بااستفاده از روش HealthConnectClient.getSdkStatus() می‌توانید دردسترس بودن را بررسی کنید.

نحوه بررسی دردسترس بودن Health Connect

fun checkHealthConnectAvailability(context: Context) {
    val providerPackageName = "com.google.android.apps.healthdata" // Or get from HealthConnectClient.DEFAULT_PROVIDER_PACKAGE_NAME
    val availabilityStatus = HealthConnectClient.getSdkStatus(context, providerPackageName)

    if (availabilityStatus == HealthConnectClient.SDK_UNAVAILABLE) {
      // Health Connect is not available. Guide the user to install/enable it.
      // For example, show a dialog.
      return // early return as there is no viable integration
    }
    if (availabilityStatus == HealthConnectClient.SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED) {
      // Health Connect is available but requires an update.
      // Optionally redirect to package installer to find a provider, for example:
      val uriString = "market://details?id=$providerPackageName&url=healthconnect%3A%2F%2Fonboarding"
      context.startActivity(
        Intent(Intent.ACTION_VIEW).apply {
          setPackage("com.android.vending")
          data = Uri.parse(uriString)
          putExtra("overlay", true)
          putExtra("callerId", context.packageName)
        }
      )
      return
    }
    // Health Connect is available, obtain a HealthConnectClient instance
    val healthConnectClient = HealthConnectClient.getOrCreate(context)
    // Issue operations with healthConnectClient
}

بسته به وضعیتی که getSdkStatus() برمی‌گرداند، می‌توانید کاربر را راهنمایی کنید تا درصورت لزوم «اتصال به خدمات بهداشتی» را از «فروشگاه Google Play» نصب یا به‌روزرسانی کند.

دردسترس بودن ویژگی

برای تعیین اینکه آیا دستگاه کاربر از طرح‌های تمرینی در «اتصال به Health» پشتیبانی می‌کند یا نه، دردسترس بودن FEATURE_PLANNED_EXERCISE را در مشتری بررسی کنید:

if (healthConnectClient
     .features
     .getFeatureStatus(
       HealthConnectFeatures.FEATURE_PLANNED_EXERCISE
     ) == HealthConnectFeatures.FEATURE_STATUS_AVAILABLE) {

  // Feature is available
} else {
  // Feature isn't available
}
برای کسب اطلاعات بیشتر، بررسی دردسترس بودن ویژگی را ببینید.

مجوزهای لازم

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

  • android.permission.health.READ_PLANNED_EXERCISE
  • android.permission.health.WRITE_PLANNED_EXERCISE

برای افزودن قابلیت تمرین برنامه‌ریزی‌شده به برنامه‌تان، با درخواست اجازه برای نوع داده PlannedExerciseSession شروع کنید.

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

<application>
  <uses-permission
android:name="android.permission.health.WRITE_PLANNED_EXERCISE" />
...
</application>

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

<application>
  <uses-permission
android:name="android.permission.health.READ_PLANNED_EXERCISE" />
...
</application>

درخواست اجازه‌ها از کاربر

پس‌از ایجاد نمونه کارخواه، برنامه شما باید از کاربر اجازه درخواست کند. کاربران باید بتوانند در هر زمانی اجازه‌ها را اعطا یا رد کنند. برای انجام این کار، مجموعه‌ای از اجازه‌ها را برای انواع داده موردنیاز ایجاد کنید. ابتدا مطمئن شوید که اجازه‌های موجود در مجموعه در مانیفست Android شما اعلام شده باشد.

val permissions =
    setOf(
        HealthPermission.getReadPermission(HeartRateRecord::class),
        HealthPermission.getWritePermission(HeartRateRecord::class),
        HealthPermission.getReadPermission(PlannedExerciseSessionRecord::class),
        HealthPermission.getWritePermission(PlannedExerciseSessionRecord::class),
        HealthPermission.getReadPermission(ExerciseSessionRecord::class),
        HealthPermission.getWritePermission(ExerciseSessionRecord::class)
    )
از getGrantedPermissions استفاده کنید تا ببینید آیا برنامه شما ازقبل اجازه‌های لازم را دارد یا نه. درغیراین‌صورت، از createRequestPermissionResultContract برای درخواست این اجازه‌ها استفاده کنید. با این کار، صفحه اجازه‌های Health Connect نمایش داده می‌شود.
val permissions = setOf(
        HealthPermission.getReadPermission(StepsRecord::class),
        HealthPermission.getWritePermission(StepsRecord::class),
        HealthPermission.getReadPermission(HeartRateRecord::class),
        HealthPermission.getWritePermission(HeartRateRecord::class)
    )

val requestPermissionsLauncher = rememberLauncherForActivityResult(
    contract = PermissionController.createRequestPermissionResultContract()
) { grantedPermissions ->
    if (grantedPermissions.containsAll(permissions)) {
        coroutineScope.launch { snackbarHostState.showSnackbar("Permissions granted!") }
    } else {
        coroutineScope.launch { snackbarHostState.showSnackbar("Permissions denied.") }
    }
}
ازآنجایی‌که کاربران می‌توانند در هر زمانی اجازه‌ها را اعطا یا لغو کنند، برنامه شما باید هر بار قبل‌از استفاده از اجازه‌ها، آن‌ها را بررسی کند و سناریوهایی را که اجازه ازدست می‌رود مدیریت کند.

طرح‌های تمرین به جلسات تمرین پیوند داده می‌شوند. بنابراین، کاربر باید برای استفاده از هر نوع سابقه مربوط به طرح تمرینی اجازه بدهد تا بتواند به‌طور کامل از این ویژگی Health Connect استفاده کند.

برای مثال، اگر طرح تمرینی ضربان قلب کاربر را درطول مجموعه‌ای از دویدن‌ها اندازه‌گیری کند، توسعه‌دهنده باید اجازه‌های زیر را اعلام کند و کاربر باید آن‌ها را اعطا کند تا بتوان جلسه تمرین را نوشت و نتایج را برای ارزیابی بعدی خواند:

  • android.permission.health.READ_EXERCISE
  • android.permission.health.READ_EXERCISE_ROUTES
  • android.permission.health.READ_HEART_RATE
  • android.permission.health.WRITE_EXERCISE
  • android.permission.health.WRITE_EXERCISE_ROUTE
  • android.permission.health.WRITE_HEART_RATE

بااین‌حال، اغلب برنامه‌ای که طرح‌های تمرینی ایجاد می‌کند و عملکرد را درمقایسه با طرح‌ها ارزیابی می‌کند با برنامه‌ای که طرح‌های تمرینی را مصرف می‌کند و داده‌های تمرین واقعی را می‌نویسد یکسان نیست. بسته به نوع برنامه، همه اجازه‌های خواندن و نوشتن لازم نیست. برای مثال، ممکن است فقط به این اجازه‌ها برای هر نوع برنامه نیاز داشته باشید:

برنامه طرح تمرین برنامه تمرین
WRITE_PLANNED_EXERCISE READ_PLANNED_EXERCISE
READ_EXERCISE WRITE_EXERCISE
READ_EXERCISE_ROUTES WRITE_EXERCISE_ROUTE
READ_HEART_RATE WRITE_HEART_RATE

اطلاعات موجود در سابقه جلسه تمرین برنامه‌ریزی‌شده

  • عنوان جلسه.
  • فهرست بلوک‌های تمرین برنامه‌ریزی‌شده.
  • زمان شروع و پایان جلسه.
  • نوع تمرین.
  • یادداشت‌های فعالیت.
  • فراداده.
  • شناسه جلسه تمرین تکمیل‌شده — این شناسه به‌طور خودکار پس‌از تکمیل جلسه تمرین مربوط به این جلسه تمرین برنامه‌ریزی‌شده نوشته می‌شود.

اطلاعات موجود در سابقه واحد تمرین برنامه‌ریزی‌شده

بلوک تمرین برنامه‌ریزی‌شده حاوی فهرستی از مراحل تمرین است تا از تکرار گروه‌های مختلف مراحل پشتیبانی کند (برای مثال، انجام توالی پنج‌تایی از تمرین‌های خم کردن بازو، بارپی، و کرانچ).

اطلاعات موجود در سابقه گام تمرین برنامه‌ریزی‌شده

تجمع‌های پشتیبانی‌شده

برای این نوع داده، تجمیع پشتیبانی‌شده‌ای وجود ندارد.

نمونه استفاده

فرض کنید کاربری برای دو روز بعد یک دویدن ۹۰ دقیقه‌ای برنامه‌ریزی می‌کند. این تمرین شامل سه دور در اطراف دریاچه با ضربان قلب هدف بین ۹۰ تا ۱۱۰ ضربه در دقیقه است.

  1. جلسه تمرین برنامه‌ریزی‌شده با موارد زیر توسط کاربر در برنامه طرح تمرین تعریف می‌شود:
    1. شروع و پایان برنامه‌ریزی‌شده اجرا
    2. نوع تمرین (دویدن)
    3. تعداد دورها (تکرارها)
    4. هدف عملکرد برای ضربان قلب (بین ۹۰ تا ۱۱۰ ضربه در دقیقه)
  2. این اطلاعات در بلوک‌های تمرین و قدم‌ها گروه‌بندی می‌شود و برنامه طرح تمرین آن را به‌عنوان PlannedExerciseSessionRecord در Health Connect می‌نویسد.
  3. کاربر جلسه برنامه‌ریزی‌شده را انجام می‌دهد (در حال اجرا).
  4. داده‌های تمرین مربوط به جلسه به یکی از روش‌های زیر ضبط می‌شود:
    1. توسط دستگاه پوشیدنی درطول جلسه. برای مثال، ضربان قلب. این داده‌ها به‌عنوان نوع سابقه برای فعالیت در Health Connect نوشته می‌شود. در این مورد، HeartRateRecord.
    2. به‌صورت دستی توسط کاربر پس‌از جلسه. برای مثال، نشان دادن شروع و پایان اجرای واقعی. این داده‌ها به‌عنوان ExerciseSessionRecord در Health Connect نوشته می‌شود.
  5. در زمانی دیگر، برنامه طرح تمرین داده‌ها را از Health Connect می‌خواند تا عملکرد واقعی را درمقایسه با اهداف تعیین‌شده توسط کاربر در جلسه تمرین برنامه‌ریزی‌شده ارزیابی کند.

تمرین‌ها را برنامه‌ریزی کنید و هدف تعیین کنید

کاربر می‌تواند تمرین خود را در آینده برنامه‌ریزی کند و هدف تعیین کند. این را به‌عنوان جلسه تمرین برنامه‌ریزی‌شده در Health Connect بنویس.

در مثال شرح‌داده‌شده در مثال استفاده، کاربر دو روز دیگر قصد دارد ۹۰ دقیقه بدود. این دویدن شامل سه دور اطراف دریاچه با ضربان قلب هدف بین ۹۰ تا ۱۱۰ ضربه در دقیقه است.

تکه‌کدی مانند این ممکن است در مدیریت‌کننده فرم برنامه‌ای که جلسات تمرین برنامه‌ریزی‌شده را در Health Connect ثبت می‌کند پیدا شود. این کد همچنین می‌تواند در نقطه ورودی برای یکپارچه‌سازی‌ها، مثلاً با سرویسی که آموزش ارائه می‌دهد، پیدا شود.

// Verify the user has granted all necessary permissions for this task
val grantedPermissions =
    healthConnectClient.permissionController.getGrantedPermissions()

if (!grantedPermissions.contains(
        HealthPermission.getWritePermission(PlannedExerciseSessionRecord::class))) {
    // The user hasn't granted the app permission to write planned exercise session data.
    Log.w("HealthConnect", "Write permission for PlannedExerciseSessionRecord not granted.")
    return
}

val plannedExerciseSessionRecord = PlannedExerciseSessionRecord(
    startTime = startTime,
    endTime = endTime,
    exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_RUNNING,
    blocks = listOf(
        PlannedExerciseBlock(
            repetitions = 1, steps = listOf(
                PlannedExerciseStep(
                    exerciseType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_RUNNING,
                    exercisePhase = PlannedExerciseStep.EXERCISE_PHASE_ACTIVE,
                    completionGoal = ExerciseCompletionGoal.RepetitionsGoal(repetitions = 3),
                    performanceTargets = listOf(
                        ExercisePerformanceTarget.HeartRateTarget(
                            minHeartRate = 90.0, maxHeartRate = 110.0
                        )
                    )
                ),
            ), description = "Three laps around the lake"
        )
    ),
    title = "Run at lake",
    notes = null,
    metadata = Metadata.activelyRecorded(
        device = Device(type = Device.Companion.TYPE_PHONE),
    ),
    startZoneOffset = null,
    endZoneOffset = null,
)

try {
    // Attempt to insert the record
    val response = healthConnectClient.insertRecords(listOf(plannedExerciseSessionRecord))

    // If execution reaches here, the insert succeeded.
    // Safely extract the ID using firstOrNull()
    val insertedPlannedExerciseSessionId = response.recordIdsList.firstOrNull()

    if (insertedPlannedExerciseSessionId != null) {
        Log.d("HealthConnect", "Successfully inserted planned exercise session ID: $insertedPlannedExerciseSessionId")
    } else {
        Log.w("HealthConnect", "Insertion succeeded but no record IDs were returned.")
    }

} catch (e: Exception) {
    // Handle API failures, database errors, or system issues safely without crashing
    Log.e("HealthConnect", "Failed to insert planned exercise session record", e)
}

ثبت داده‌های فعالیت و تمرین

دو روز بعد، کاربر جلسه تمرین واقعی را ثبت می‌کند. این را به‌عنوان جلسه تمرین در Health Connect بنویس.

در این مثال، مدت جلسه کاربر دقیقاً با مدت برنامه‌ریزی‌شده مطابقت داشت.

تکه‌کد زیر ممکن است در مدیریت‌کننده فرم برنامه‌ای که جلسات تمرین را در Health Connect ثبت می‌کند پیدا شود. همچنین ممکن است در مدیریت‌کننده‌های انتقال و صادر کردن داده برای دستگاه پوشیدنی‌ای که قابلیت تشخیص و ثبت جلسات تمرین را دارد پیدا شود.

‫insertedPlannedExerciseSessionId در اینجا از مثال قبلی استفاده مجدد شده است. در برنامه واقعی، شناسه با انتخاب جلسه تمرین برنامه‌ریزی‌شده توسط کاربر از فهرست جلسات موجود تعیین می‌شود.

// Verify the user has granted all necessary permissions for this task
val grantedPermissions =
    healthConnectClient.permissionController.getGrantedPermissions()
if (!grantedPermissions.contains(
        HealthPermission.getWritePermission(ExerciseSessionRecord::class))) {
    // The user doesn't granted the app permission to write exercise session data.
    return
}

val sessionDuration = Duration.ofMinutes(90)
val sessionEndTime = Instant.now()
val sessionStartTime = sessionEndTime.minus(sessionDuration)

val exerciseSessionRecord = ExerciseSessionRecord(
    startTime = sessionStartTime,
    startZoneOffset = ZoneOffset.UTC,
    endTime = sessionEndTime,
    endZoneOffset = ZoneOffset.UTC,
    exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_RUNNING,
    segments = listOf(
        ExerciseSegment(
            startTime = sessionStartTime,
            endTime = sessionEndTime,
            repetitions = 3,
            segmentType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_RUNNING
        )
    ),
    title = "Run at lake",
    plannedExerciseSessionId = insertedPlannedExerciseSessionId,
    metadata = Metadata.activelyRecorded(
        device = Device(type = Device.Companion.TYPE_PHONE)
    )
)
val insertedExerciseSessions =
    healthConnectClient.insertRecords(listOf(exerciseSessionRecord))

دستگاه پوشیدنی همچنین ضربان قلب او را درطول دویدن ثبت می‌کند. از گزیده زیر می‌توان برای تولید کردن سوابق در محدوده هدف استفاده کرد.

در یک برنامه واقعی، بخش‌های اصلی این تکه‌کد ممکن است در مدیریت‌کننده برای پیامی از یک پوشیدنی پیدا شود، که اندازه‌گیری را پس‌از جمع‌آوری در Health Connect می‌نویسد.

// Verify the user has granted all necessary permissions for this task
val grantedPermissions =
    healthConnectClient.permissionController.getGrantedPermissions()
if (!grantedPermissions.contains(
        HealthPermission.getWritePermission(HeartRateRecord::class))) {
    // The user doesn't granted the app permission to write heart rate record data.
    return
}

val samples = mutableListOf<HeartRateRecord.Sample>()
var currentTime = sessionStartTime
while (currentTime.isBefore(sessionEndTime)) {
    val bpm = Random.nextInt(21) + 90
    val heartRateRecord = HeartRateRecord.Sample(
        time = currentTime,
        beatsPerMinute = bpm.toLong(),
    )
    samples.add(heartRateRecord)
    currentTime = currentTime.plusSeconds(180)
}

val heartRateRecord = HeartRateRecord(
    startTime = sessionStartTime,
    startZoneOffset = ZoneOffset.UTC,
    endTime = sessionEndTime,
    endZoneOffset = ZoneOffset.UTC,
    samples = samples,
    metadata = Metadata.activelyRecorded(
        device = Device(type = Device.Companion.TYPE_WATCH)
    )
)
val insertedHeartRateRecords = healthConnectClient.insertRecords(listOf(heartRateRecord))

ارزیابی اهداف عملکرد

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

تکه‌کدی مثل این احتمالاً در کار دوره‌ای برای ارزیابی هدف‌های عملکرد یا هنگام بار کردن فهرست تمرین‌ها و نمایش اعلان درباره هدف‌های عملکرد در برنامه پیدا می‌شود.

    // Verify the user has granted all necessary permissions for this task
    val grantedPermissions =
        healthConnectClient.permissionController.getGrantedPermissions()
    if (!grantedPermissions.containsAll(
            listOf(
                HealthPermission.getReadPermission(ExerciseSessionRecord::class),
                HealthPermission.getReadPermission(PlannedExerciseSessionRecord::class),
                HealthPermission.getReadPermission(HeartRateRecord::class)
            )
        )
    ) {
        // The user doesn't granted the app permission to read exercise session record data.
        return
    }

    val searchDuration = Duration.ofDays(1)
    val searchEndTime = Instant.now()
    val searchStartTime = searchEndTime.minus(searchDuration)

    val response = healthConnectClient.readRecords(
        ReadRecordsRequest<ExerciseSessionRecord>(
            timeRangeFilter = TimeRangeFilter.between(searchStartTime, searchEndTime)
        )
    )
    for (exerciseRecord in response.records) {
        val plannedExerciseRecordId = exerciseRecord.plannedExerciseSessionId
        val plannedExerciseRecord =
            if (plannedExerciseRecordId == null) null else healthConnectClient.readRecord(
                PlannedExerciseSessionRecord::class, plannedExerciseRecordId
            ).record
        if (plannedExerciseRecord != null) {
            val aggregateRequest = AggregateRequest(
                metrics = setOf(HeartRateRecord.BPM_AVG),
                timeRangeFilter = TimeRangeFilter.between(
                    exerciseRecord.startTime, exerciseRecord.endTime
                ),
            )
            val aggregationResult = healthConnectClient.aggregate(aggregateRequest)

            val maxBpm = aggregationResult[HeartRateRecord.BPM_MAX]
            val minBpm = aggregationResult[HeartRateRecord.BPM_MIN]
            if (maxBpm != null && minBpm != null) {
                plannedExerciseRecord.blocks.forEach { block ->
                    block.steps.forEach { step ->
                        step.performanceTargets.forEach { target ->
                            when (target) {
                                is ExercisePerformanceTarget.HeartRateTarget -> {
                                    val minTarget = target.minHeartRate
                                    val maxTarget = target.maxHeartRate
                                    if(
                                        minBpm >= minTarget && maxBpm <= maxTarget
                                    ) {
                                        // Success!
                                    }
                                }
                                // Handle more target types
                            }
                        }
                    }
                }
            }
        }
    }
}

جلسه‌های تمرین

جلسه‌های تمرین می‌تواند شامل هر چیزی از دویدن تا بدمینتون باشد.

نوشتن داده‌های جلسه تمرین

به این صورت می‌توانید درخواست درج را که شامل جلسه است بسازید:

suspend fun writeExerciseSession(healthConnectClient: HealthConnectClient) {
    healthConnectClient.insertRecords(
        listOf(
            ExerciseSessionRecord(
                startTime = START_TIME,
                startZoneOffset = START_ZONE_OFFSET,
                endTime = END_TIME,
                endZoneOffset = END_ZONE_OFFSET,
                exerciseType = ExerciseSessionRecord.ExerciseType.RUNNING,
                title = "My Run",
                metadata = Metadata.manualEntry()
            ),
            // ... other records
        )
    )
}

خواندن جلسه تمرین

در اینجا مثالی از نحوه خواندن جلسه تمرین آورده شده است:

suspend fun readExerciseSessions(
    healthConnectClient: HealthConnectClient,
    startTime: Instant,
    endTime: Instant
) {
    val response =
        healthConnectClient.readRecords(
            ReadRecordsRequest(
                ExerciseSessionRecord::class,
                timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
            )
        )
    for (exerciseRecord in response.records) {
        // Process each exercise record
        // Optionally pull in with other data sources of the same time range.
        val distanceRecord =
            healthConnectClient
                .readRecords(
                    ReadRecordsRequest(
                        DistanceRecord::class,
                        timeRangeFilter =
                            TimeRangeFilter.between(
                                exerciseRecord.startTime,
                                exerciseRecord.endTime
                            )
                    )
                )
                .records
    }
}

نوشتن داده‌های زیرنوع

جلسه‌ها همچنین می‌توانند شامل داده‌های اختیاری زیرنوع باشند که جلسه را با اطلاعات اضافی غنی می‌کنند.

برای مثال، جلسات تمرین می‌تواند شامل کلاس‌های ExerciseSegment، ExerciseLap، و ExerciseRoute باشد:

val segments = listOf(
  ExerciseSegment(
    startTime = Instant.parse("2022-01-02T10:10:10Z"),
    endTime = Instant.parse("2022-01-02T10:10:13Z"),
    segmentType = ActivitySegmentType.BENCH_PRESS,
    repetitions = 373
  )
)

val laps = listOf(
  ExerciseLap(
    startTime = Instant.parse("2022-01-02T10:10:10Z"),
    endTime = Instant.parse("2022-01-02T10:10:13Z"),
    length = 0.meters
  )
)

ExerciseSessionRecord(
  exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_CALISTHENICS,
    startTime = Instant.parse("2022-01-02T10:10:10Z"),
    endTime = Instant.parse("2022-01-02T10:10:13Z"),
  startZoneOffset = ZoneOffset.UTC,
  endZoneOffset = ZoneOffset.UTC,
  segments = segments,
  laps = laps,
  route = route,
  metadata = Metadata.manualEntry()
)