Program latihan

Panduan ini kompatibel dengan Health Connect versi 1.1.0-alpha12 atau yang lebih baru.

Health Connect menyediakan jenis data latihan terencana untuk memungkinkan aplikasi pelatihan menulis rencana pelatihan dan memungkinkan aplikasi olahraga membaca rencana pelatihan. Latihan yang dicatat (olahraga) dapat dibaca kembali untuk analisis performa yang dipersonalisasi guna membantu pengguna mencapai sasaran pelatihan mereka.

Memeriksa ketersediaan Health Connect

Sebelum mencoba menggunakan Health Connect, aplikasi Anda harus memverifikasi bahwa Health Connect tersedia di perangkat pengguna. Health Connect mungkin tidak diinstal di semua perangkat atau mungkin dinonaktifkan. Anda dapat memeriksa ketersediaan menggunakan metode HealthConnectClient.getSdkStatus().

Cara memeriksa ketersediaan Health Connect

fun checkHealthConnectAvailability(context: Context) {
    val providerPackageName = "com.google.android.apps.healthdata"
    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
}

Bergantung pada status yang ditampilkan oleh getSdkStatus(), Anda dapat memandu pengguna untuk menginstal atau mengupdate Health Connect dari Google Play Store jika perlu.

Ketersediaan fitur

Untuk menentukan apakah perangkat pengguna mendukung paket latihan di Health Connect, periksa ketersediaan FEATURE_PLANNED_EXERCISE di klien:

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

  // Feature is available
} else {
  // Feature isn't available
}
Lihat Memeriksa ketersediaan fitur untuk mempelajari lebih lanjut.

Izin yang diperlukan

Akses ke latihan terencana dilindungi oleh izin berikut:

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

Untuk menambahkan kemampuan latihan terencana ke aplikasi Anda, mulailah dengan meminta izin untuk jenis data PlannedExerciseSession.

Berikut adalah izin yang harus Anda deklarasikan agar dapat menulis latihan terencana:

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

Untuk membaca latihan terencana, Anda perlu meminta izin berikut:

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

Meminta izin dari pengguna

Setelah membuat instance klien, aplikasi Anda perlu meminta izin dari pengguna. Pengguna harus diizinkan untuk memberikan atau menolak izin kapan saja. Untuk melakukannya, buat kumpulan izin untuk jenis data yang diperlukan. Pastikan izin dalam kumpulan dideklarasikan dalam manifes Android Anda terlebih dahulu.

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)
    )
Gunakan getGrantedPermissions untuk mengetahui apakah aplikasi Anda sudah mendapatkan izin yang diperlukan. Jika belum, gunakan createRequestPermissionResultContract untuk meminta izin tersebut. Tindakan ini akan menampilkan layar izin 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.") }
    }
}
Karena pengguna dapat memberikan atau mencabut izin kapan saja, aplikasi Anda harus memeriksa izin setiap kali sebelum menggunakannya dan menangani skenario saat izin hilang.

Rencana pelatihan ditautkan ke sesi latihan. Oleh karena itu, pengguna harus memberikan izin untuk menggunakan setiap jenis data yang terkait dengan rencana latihan agar dapat sepenuhnya memanfaatkan fitur Health Connect ini.

Misalnya, jika rencana latihan mengukur detak jantung pengguna selama serangkaian lari, izin berikut mungkin perlu dideklarasikan oleh developer dan diberikan oleh pengguna untuk menulis sesi latihan dan membaca hasilnya untuk evaluasi nanti:

  • 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

Namun, sering kali aplikasi yang membuat rencana latihan dan mengevaluasi performa berdasarkan rencana tidak sama dengan aplikasi yang menggunakan rencana latihan dan menulis data latihan yang sebenarnya. Bergantung pada jenis aplikasi, tidak semua izin baca dan tulis akan diperlukan. Misalnya, Anda mungkin hanya memerlukan izin ini untuk setiap jenis aplikasi:

Aplikasi rencana pelatihan Aplikasi olahraga
WRITE_PLANNED_EXERCISE READ_PLANNED_EXERCISE
READ_EXERCISE WRITE_EXERCISE
READ_EXERCISE_ROUTES WRITE_EXERCISE_ROUTE
READ_HEART_RATE WRITE_HEART_RATE

Informasi yang disertakan dalam kumpulan data sesi latihan terencana

  • Judul sesi.
  • Daftar blok latihan terencana.
  • Waktu mulai dan berakhirnya sesi.
  • Jenis latihan.
  • Catatan untuk aktivitas.
  • Metadata.
  • ID sesi latihan yang telah selesai — ID ini ditulis secara otomatis setelah sesi latihan yang terkait dengan sesi latihan yang direncanakan ini selesai.

Informasi yang disertakan dalam kumpulan data blok latihan terencana

Blok latihan terencana berisi daftar langkah latihan, untuk mendukung pengulangan berbagai kelompok langkah (misalnya, melakukan urutan arm curl, burpee, dan crunch lima kali berturut-turut).

Informasi yang disertakan dalam kumpulan data langkah latihan terencana

Agregasi yang didukung

Tidak ada agregasi yang didukung untuk jenis data ini.

Contoh penggunaan

Misalnya, pengguna merencanakan lari selama 90 menit dua hari dari sekarang. Lari ini akan menampilkan tiga putaran di sekitar danau dengan target detak jantung antara 90 dan 110 bpm.

  1. Sesi latihan terencana dengan hal berikut ditentukan oleh pengguna di aplikasi rencana pelatihan:
    1. Rencana awal dan akhir lari
    2. Jenis latihan (berlari)
    3. Jumlah putaran (pengulangan)
    4. Target performa untuk detak jantung (antara 90 dan 110 bpm)
  2. Informasi ini dikelompokkan ke dalam blok dan langkah latihan, lalu ditulis ke Health Connect oleh aplikasi rencana latihan sebagai PlannedExerciseSessionRecord.
  3. Pengguna melakukan sesi yang direncanakan (berlari).
  4. Data olahraga yang terkait dengan sesi dicatat:
    1. Oleh perangkat wearable selama sesi. Misalnya, detak jantung. Data ini ditulis ke Health Connect sebagai jenis data untuk aktivitas. Dalam kasus ini, HeartRateRecord.
    2. Secara manual oleh pengguna setelah sesi. Misalnya, menunjukkan awal dan akhir lari yang sebenarnya. Data ini ditulis ke Health Connect sebagai ExerciseSessionRecord.
  5. Di lain waktu, aplikasi rencana latihan akan membaca data dari Health Connect untuk mengevaluasi performa aktual terhadap target yang ditetapkan oleh pengguna dalam sesi latihan yang direncanakan.

Merencanakan latihan dan menetapkan target

Pengguna dapat merencanakan latihan mereka di masa mendatang dan menetapkan target. Tulis ini ke Health Connect sebagai sesi latihan terencana.

Dalam contoh yang dijelaskan dalam Contoh penggunaan, pengguna merencanakan lari selama 90 menit dua hari dari sekarang. Lari ini akan menampilkan tiga putaran di sekitar danau dengan target detak jantung antara 90 dan 110 bpm.

Cuplikan seperti ini dapat ditemukan di pengendali formulir untuk aplikasi yang mencatat sesi latihan yang direncanakan ke Health Connect. ID ini juga dapat ditemukan di titik penyerapan untuk integrasi, misalnya dengan layanan yang menawarkan pelatihan.

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

Mencatat data latihan dan aktivitas

Dua hari kemudian, pengguna mencatat sesi latihan yang sebenarnya. Tulis data ini ke Health Connect sebagai sesi latihan.

Dalam contoh ini, durasi sesi pengguna sama persis dengan durasi yang direncanakan.

Cuplikan berikut mungkin ditemukan di pengendali formulir untuk aplikasi yang mencatat sesi latihan ke Health Connect. Hal ini juga dapat ditemukan di pengendali penyerapan dan ekspor data untuk perangkat wearable yang mampu mendeteksi dan mencatat sesi latihan.

insertedPlannedExerciseSessionId di sini digunakan kembali dari contoh sebelumnya. Dalam aplikasi yang sebenarnya, ID akan ditentukan oleh pengguna yang memilih sesi latihan terencana dari daftar sesi yang ada.

// 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))

Perangkat wearable juga mencatat detak jantungnya selama berlari. Cuplikan berikut dapat digunakan untuk membuat data dalam rentang target.

Dalam aplikasi sungguhan, bagian utama cuplikan ini mungkin ditemukan di pengendali untuk pesan dari perangkat wearable, yang akan menulis pengukuran ke Health Connect setelah pengumpulan.

// 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))

Mengevaluasi target performa

Sehari setelah latihan pengguna, Anda dapat mengambil latihan yang dicatat, memeriksa target latihan yang direncanakan, dan mengevaluasi jenis data tambahan untuk menentukan apakah target yang ditetapkan telah terpenuhi.

Cuplikan seperti ini kemungkinan akan ditemukan dalam tugas berkala untuk mengevaluasi target performa atau saat memuat daftar latihan dan menampilkan notifikasi tentang target performa di aplikasi.

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

Sesi olahraga

Sesi olahraga dapat mencakup apa saja, dari berlari hingga bulu tangkis.

Menulis sesi olahraga

Berikut ini cara mem-build permintaan penyisipan yang menyertakan sesi:

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

Membaca sesi olahraga

Berikut ini contoh cara membaca sesi olahraga:

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

Menulis data subjenis

Sesi juga dapat terdiri dari data subjenis opsional yang memperkaya sesi dengan informasi tambahan.

Misalnya, sesi olahraga dapat menyertakan class ExerciseSegment, ExerciseLap, dan 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()
)