Tambahkan rute latihan

Panduan ini kompatibel dengan Health Connect versi 1.1.0-alpha12.

Rute olahraga memungkinkan pengguna melacak rute GPS untuk aktivitas olahraga terkait dan membagikan peta olahraga mereka ke aplikasi lain.

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 sebelumnya 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.

Panduan ini memberikan informasi tentang cara meminta izin dari pengguna dan juga menjelaskan cara aplikasi menerima izin untuk menulis data rute sebagai bagian dari sesi olahraga.

Fungsi baca dan tulis untuk rute olahraga mencakup:

  1. Aplikasi membuat izin tulis baru untuk rute olahraga.
  2. Penyisipan dilakukan dengan menulis sesi olahraga beserta rute sebagai kolomnya.
  3. Membaca:
    1. Untuk pemilik sesi, data diakses menggunakan pembacaan sesi.
    2. Dari aplikasi pihak ketiga, melalui dialog yang memungkinkan pengguna memberikan pembacaan rute satu kali.

Jika pengguna tidak memiliki izin tulis dan rute tidak ditetapkan, rute tidak akan diperbarui.

Jika aplikasi Anda memiliki izin tulis rute dan mencoba memperbarui sesi dengan meneruskan objek sesi tanpa rute, rute yang sudah ada akan dihapus.

Ketersediaan fitur

Untuk menentukan apakah perangkat pengguna mendukung olahraga yang direncanakan 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 rute olahraga dilindungi oleh izin berikut:

  • android.permission.health.READ_EXERCISE_ROUTES
  • android.permission.health.WRITE_EXERCISE_ROUTE
Catatan: Untuk jenis izin ini, READ_EXERCISE_ROUTES adalah bentuk jamak, sedangkan WRITE_EXERCISE_ROUTE adalah bentuk tunggal.

Untuk menambahkan kemampuan rute olahraga ke aplikasi Anda, mulailah dengan meminta izin untuk jenis data ExerciseSession.

Berikut adalah izin yang harus Anda deklarasikan agar dapat menulis rute olahraga:

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

Untuk membaca rute olahraga, Anda harus meminta izin berikut:

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

Anda juga harus mendeklarasikan izin olahraga, karena setiap rute dikaitkan dengan sesi olahraga (satu sesi = satu olahraga).

Untuk meminta izin, gunakan metode PermissionController.createRequestPermissionResultContract() saat Anda pertama kali menghubungkan aplikasi ke Health Connect. Beberapa izin yang mungkin ingin Anda minta adalah:

  • Membaca data kesehatan dan kebugaran, termasuk data rute: HealthPermission.getReadPermission(ExerciseSessionRecord::class)
  • Menulis data kesehatan dan kebugaran, termasuk data rute: HealthPermission.getWritePermission(ExerciseSessionRecord::class)
  • Menulis data rute olahraga: HealthPermission.PERMISSION_WRITE_EXERCISE_ROUTE

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(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 perlu memeriksa izin setiap kali sebelum menggunakannya dan menangani skenario saat izin hilang.

Informasi yang disertakan dalam data sesi olahraga

Setiap data sesi olahraga berisi informasi berikut:

  • **Jenis** olahraga, misalnya, bersepeda.
  • **Rute** olahraga, yang berisi informasi seperti lintang, bujur, dan ketinggian.

Agregasi yang didukung

Nilai agregat berikut tersedia untuk ExerciseSessionRecord:

Contoh penggunaan

Cuplikan kode berikut menunjukkan cara membaca dan menulis rute olahraga.

Baca rute olahraga

Aplikasi Anda tidak dapat membaca data rute olahraga yang dibuat oleh aplikasi lain saat berjalan di latar belakang.

Saat aplikasi Anda berjalan di latar belakang dan mencoba membaca rute olahraga yang dibuat oleh aplikasi lain, Health Connect akan menampilkan respons ExerciseRouteResult.ConsentRequired, meskipun aplikasi Anda memiliki akses Selalu izinkan ke data rute olahraga.

Oleh karena itu, sebaiknya minta rute saat pengguna berinteraksi dengan aplikasi Anda secara sengaja, saat pengguna aktif menggunakan UI aplikasi Anda.

Untuk mempelajari pembacaan latar belakang lebih lanjut, lihat Contoh pembacaan latar belakang.

Cuplikan kode berikut menunjukkan cara membaca sesi di Health Connect dan meminta rute dari sesi tersebut:

private suspend fun readExerciseSessionAndRoute() {
    val client = healthConnectClient ?: return

    val endTime = Instant.now()
    val startTime = endTime.minus(Duration.ofHours(1))

    val grantedPermissions = client.permissionController.getGrantedPermissions()

    // 1. Verify basic Exercise Session permissions
    if (!grantedPermissions.contains(
            HealthPermission.getReadPermission(ExerciseSessionRecord::class)
        )
    ) {
        return
    }

    // 2. Read the sessions
    val readResponse = client.readRecords(
        ReadRecordsRequest(
            ExerciseSessionRecord::class,
            TimeRangeFilter.between(startTime, endTime)
        )
    )

    val exerciseRecord = readResponse.records.firstOrNull() ?: return
    val recordId = exerciseRecord.metadata.id

    // 3. Read the specific record to check for the route
    val sessionResponse = client.readRecord(ExerciseSessionRecord::class, recordId)

    // 4. Handle the Route Result directly from the response
    when (val routeResult = sessionResponse.record.exerciseRouteResult) {
        is ExerciseRouteResult.Data -> {
            displayExerciseRoute(routeResult.exerciseRoute)
        }
        is ExerciseRouteResult.ConsentRequired -> {
            // Since you are in a Service, you cannot launch ActivityResultLauncher.
            // Send a notification to the user to grant route-specific consent.
            handleConsentRequired(recordId)
        }
        is ExerciseRouteResult.NoData -> Unit
        else -> Unit
    }
}

private fun displayExerciseRoute(route: ExerciseRoute) {
    val locations = route.route.orEmpty()
    for (location in locations) {
        println(location)
    }
}

Menulis rute olahraga

Kode berikut menunjukkan cara merekam sesi yang menyertakan rute olahraga:

private suspend fun insertExerciseRoute() {
    val client = healthConnectClient ?: return

    val grantedPermissions = client.permissionController.getGrantedPermissions()

    // 1. Verify Session Write Permission
    val hasWriteSession = grantedPermissions.contains(
        HealthPermission.getWritePermission(ExerciseSessionRecord::class)
    )
    if (!hasWriteSession) return

    val sessionStartTime = Instant.now()
    val sessionDuration = Duration.ofMinutes(20)
    val sessionEndTime = sessionStartTime.plus(sessionDuration)

    // 2. Build the route if route-specific write permission is granted
    val hasWriteRoute = grantedPermissions.contains(HealthPermission.PERMISSION_WRITE_EXERCISE_ROUTE)

    val exerciseRoute = if (hasWriteRoute) {
        ExerciseRoute(
            listOf(
                ExerciseRoute.Location(
                    time = sessionStartTime,
                    latitude = 6.5483,
                    longitude = 0.5488,
                    horizontalAccuracy = Length.meters(2.0),
                    verticalAccuracy = Length.meters(2.0),
                    altitude = Length.meters(9.0),
                ),
                ExerciseRoute.Location(
                    time = sessionEndTime.minusSeconds(1),
                    latitude = 6.4578,
                    longitude = 0.6577,
                    horizontalAccuracy = Length.meters(2.0),
                    verticalAccuracy = Length.meters(2.0),
                    altitude = Length.meters(9.2),
                )
            )
        )
    } else {
        null
    }

    // 3. Create the session record
    val exerciseSessionRecord = ExerciseSessionRecord(
        startTime = sessionStartTime,
        startZoneOffset = ZoneOffset.UTC,
        endTime = sessionEndTime,
        endZoneOffset = ZoneOffset.UTC,
        exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_BIKING,
        title = "Morning Bike Ride",
        exerciseRoute = exerciseRoute,
        metadata = Metadata(
            device = Device(type = Device.TYPE_PHONE)
        )
    )

    // 4. Insert into Health Connect
    client.insertRecords(listOf(exerciseSessionRecord))
}

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

Menghapus sesi olahraga

Ada dua cara untuk menghapus sesi olahraga:

  1. Menurut rentang waktu.
  2. Menurut UID.

Berikut ini cara menghapus data subjenis berdasarkan rentang waktu:

suspend fun deleteExerciseSessionByTimeRange(
    healthConnectClient: HealthConnectClient,
    exerciseRecord: ExerciseSessionRecord,
) {
    val timeRangeFilter = TimeRangeFilter.between(exerciseRecord.startTime, exerciseRecord.endTime)
    healthConnectClient.deleteRecords(ExerciseSessionRecord::class, timeRangeFilter)
    // delete the associated distance record
    healthConnectClient.deleteRecords(DistanceRecord::class, timeRangeFilter)
}

Anda juga dapat menghapus data subjenis menurut UID. Melakukan hal tersebut hanya akan menghapus sesi olahraga, bukan data terkait:

suspend fun deleteExerciseSessionByUid(
    healthConnectClient: HealthConnectClient,
    exerciseRecord: ExerciseSessionRecord,
) {
    healthConnectClient.deleteRecords(
        ExerciseSessionRecord::class,
        recordIdsList = listOf(exerciseRecord.metadata.id),
        clientRecordIdsList = emptyList()
    )
}