Health Connect menyediakan jenis data olahraga terencana untuk memungkinkan aplikasi pelatihan menulis rencana pelatihan dan memungkinkan aplikasi olahraga membaca rencana pelatihan. Olahraga (latihan fisik) yang direkam 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 secara default di semua perangkat atau dapat 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" // 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 }
Bergantung pada status yang ditampilkan oleh getSdkStatus(), Anda dapat memandu pengguna untuk menginstal atau mengupdate Health Connect dari Google Play Store jika diperlukan.
Ketersediaan fitur
Untuk menentukan apakah perangkat pengguna mendukung rencana pelatihan di Health Connect, periksa ketersediaanFEATURE_PLANNED_EXERCISE di klien:
if (healthConnectClient
.features
.getFeatureStatus(
HealthConnectFeatures.FEATURE_PLANNED_EXERCISE
) == HealthConnectFeatures.FEATURE_STATUS_AVAILABLE) {
// Feature is available
} else {
// Feature isn't available
}
Izin yang diperlukan
Akses ke olahraga terencana dilindungi oleh izin berikut:
android.permission.health.READ_PLANNED_EXERCISEandroid.permission.health.WRITE_PLANNED_EXERCISE
Untuk menambahkan kemampuan olahraga terencana ke aplikasi Anda, mulailah dengan meminta izin untuk jenis data PlannedExerciseSession.
Berikut adalah izin yang harus Anda deklarasikan agar dapat menulis olahraga terencana:
<application>
<uses-permission
android:name="android.permission.health.WRITE_PLANNED_EXERCISE" />
...
</application>
Untuk membaca olahraga terencana, Anda harus 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) )
getGrantedPermissions
untuk mengetahui apakah aplikasi Anda sudah mendapatkan izin yang diperlukan. Jika tidak, 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.") } } }
Izin terkait
Rencana pelatihan ditautkan ke sesi olahraga. Oleh karena itu, pengguna harus memberikan izin untuk menggunakan setiap jenis data yang terkait dengan rencana pelatihan agar dapat memanfaatkan fitur Health Connect ini sepenuhnya.
Misalnya, jika rencana pelatihan mengukur detak jantung pengguna selama serangkaian lari, izin berikut mungkin perlu dideklarasikan oleh developer dan diberikan oleh pengguna untuk menulis sesi olahraga dan membaca hasilnya untuk evaluasi nanti:
android.permission.health.READ_EXERCISEandroid.permission.health.READ_EXERCISE_ROUTESandroid.permission.health.READ_HEART_RATEandroid.permission.health.WRITE_EXERCISEandroid.permission.health.WRITE_EXERCISE_ROUTEandroid.permission.health.WRITE_HEART_RATE
Namun, sering kali aplikasi yang membuat rencana pelatihan dan mengevaluasi performa terhadap rencana tidak sama dengan aplikasi yang menggunakan rencana pelatihan dan menulis data olahraga 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 data sesi olahraga terencana
- Judul sesi.
- Daftar blok olahraga terencana.
- Waktu mulai dan berakhir sesi.
- Jenis olahraga.
- Catatan untuk aktivitas.
- Metadata.
- ID sesi olahraga yang telah selesai — ID ini ditulis secara otomatis setelah sesi olahraga yang terkait dengan sesi olahraga terencana ini selesai.
Informasi yang disertakan dalam data blok olahraga terencana
Blok olahraga terencana berisi daftar langkah olahraga, untuk mendukung pengulangan berbagai kelompok langkah (misalnya, lakukan urutan latihan angkat beban, burpee, dan crunch lima kali berturut-turut).
- Deskripsi blok.
- Daftar langkah olahraga terencana.
- Jumlah pengulangan.
Informasi yang disertakan dalam data langkah olahraga terencana
- Deskripsi langkah.
- Kategori olahraga.
- Jenis olahraga.
- Daftar target performa.
- Sasaran penyelesaian.
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 detak jantung target antara 90 dan 110 bpm.
- Sesi olahraga terencana dengan hal berikut ditentukan oleh pengguna di aplikasi rencana pelatihan:
- Awal dan akhir lari yang direncanakan
- Jenis olahraga (lari)
- Jumlah putaran (pengulangan)
- Target performa untuk detak jantung (antara 90 dan 110 bpm)
- Informasi ini dikelompokkan ke dalam blok dan langkah olahraga serta ditulis ke Health Connect oleh aplikasi rencana pelatihan sebagai
PlannedExerciseSessionRecord. - Pengguna melakukan sesi yang direncanakan (lari).
- Data olahraga yang terkait dengan sesi direkam:
- Oleh perangkat wearable selama sesi. Misalnya, detak jantung.
Data ini ditulis ke Health Connect sebagai jenis data untuk aktivitas. Dalam hal ini,
HeartRateRecord. - Secara manual oleh pengguna setelah sesi. Misalnya, menunjukkan awal dan akhir lari sebenarnya. Data ini ditulis ke Health Connect sebagai
ExerciseSessionRecord.
- Oleh perangkat wearable selama sesi. Misalnya, detak jantung.
Data ini ditulis ke Health Connect sebagai jenis data untuk aktivitas. Dalam hal ini,
- Di lain waktu, aplikasi rencana pelatihan membaca data dari Health Connect untuk mengevaluasi performa sebenarnya terhadap target yang ditetapkan oleh pengguna dalam sesi olahraga terencana.
Merencanakan olahraga dan menetapkan target
Pengguna dapat merencanakan olahraga di masa mendatang dan menetapkan target. Tulis ini ke Health Connect sebagai sesi olahraga terencana.
Dalam contoh yang dijelaskan di Contoh penggunaan, pengguna merencanakan lari selama 90 menit dua hari dari sekarang. Lari ini akan menampilkan tiga putaran di sekitar danau dengan detak jantung target antara 90 dan 110 bpm.
Cuplikan seperti ini dapat ditemukan di pengendali formulir untuk aplikasi yang mencatat sesi olahraga terencana ke Health Connect. Cuplikan 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( 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 olahraga dan aktivitas
Dua hari kemudian, pengguna mencatat sesi olahraga sebenarnya. Tulis ini ke Health Connect sebagai sesi olahraga.
Dalam contoh ini, durasi sesi pengguna cocok dengan durasi yang direncanakan.
Cuplikan berikut mungkin ditemukan di pengendali formulir untuk aplikasi yang mencatat sesi olahraga ke Health Connect. Cuplikan ini juga dapat ditemukan di pengendali penyerapan dan ekspor data untuk perangkat wearable yang dapat mendeteksi dan mencatat sesi olahraga.
insertedPlannedExerciseSessionId di sini digunakan kembali dari contoh sebelumnya. Di aplikasi sebenarnya, ID akan ditentukan oleh pengguna yang memilih sesi olahraga 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( device = Device(type = Device.Companion.TYPE_PHONE) ) ) val insertedExerciseSessions = healthConnectClient.insertRecords(listOf(exerciseSessionRecord))
Perangkat wearable juga mencatat detak jantung pengguna selama lari. Cuplikan berikut dapat digunakan untuk membuat data dalam rentang target.
Di aplikasi sebenarnya, 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( device = Device(type = Device.Companion.TYPE_WATCH) ) ) val insertedHeartRateRecords = healthConnectClient.insertRecords(listOf(heartRateRecord))
Mengevaluasi target performa
Sehari setelah olahraga pengguna, Anda dapat mengambil olahraga yang dicatat, memeriksa target olahraga terencana, dan mengevaluasi jenis data tambahan untuk menentukan apakah target yang ditetapkan terpenuhi.
Cuplikan seperti ini kemungkinan akan ditemukan dalam tugas berkala untuk mengevaluasi target performa atau saat memuat daftar olahraga 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()
)