Plany treningowe

Ten przewodnik jest zgodny z wersją Health Connect 1.1.0-alpha11.

Health Connect udostępnia typ danych zaplanowane ćwiczenia, aby umożliwić aplikacjom do treningu zapisywanie planów treningowych, a aplikacjom do ćwiczeń odczytywanie tych planów. Zarejestrowane ćwiczenia (treningi) można odczytywać w celu spersonalizowanej analizy wydajności, aby pomagać użytkownikom w osiąganiu celów treningowych.

Dostępność funkcji

Aby sprawdzić, czy urządzenie użytkownika obsługuje plany treningowe w Health Connect, sprawdź dostępność FEATURE_PLANNED_EXERCISE w aplikacji:

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

  // Feature is available
} else {
  // Feature isn't available
}

Więcej informacji znajdziesz w sekcji Sprawdzanie dostępności funkcji.

Wymagane uprawnienia

Dostęp do zaplanowanych ćwiczeń jest chroniony przez te uprawnienia:

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

Aby dodać do aplikacji możliwość planowania ćwiczeń, zacznij od poproszenia o uprawnienia do zapisu w przypadku typu danych PlannedExerciseSession.

Aby móc zapisywać zaplanowane ćwiczenia, musisz zadeklarować to uprawnienie:

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

Aby odczytać zaplanowane ćwiczenie, musisz poprosić o te uprawnienia:

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

Prośba użytkownika o uprawnienia

Po utworzeniu instancji klienta aplikacja musi poprosić użytkownika o przyznanie uprawnień. Użytkownicy muszą mieć możliwość przyznania lub odmowy przyznania uprawnień w dowolnym momencie.

Aby to zrobić, utwórz zestaw uprawnień dla wymaganych typów danych. Sprawdź, czy uprawnienia w zestawie są najpierw zadeklarowane w pliku manifestu Androida.

// Create a set of permissions for required data types
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)
)

Użyj getGrantedPermissions, aby sprawdzić, czy Twoja aplikacja ma już przyznane wymagane uprawnienia. Jeśli nie, użyj createRequestPermissionResultContract, aby poprosić o te uprawnienia. Wyświetli się ekran uprawnień Health Connect.

// Create the permissions launcher
val requestPermissionActivityContract = PermissionController.createRequestPermissionResultContract()

val requestPermissions = registerForActivityResult(requestPermissionActivityContract) { granted ->
  if (granted.containsAll(PERMISSIONS)) {
    // Permissions successfully granted
  } else {
    // Lack of required permissions
  }
}

suspend fun checkPermissionsAndRun(healthConnectClient: HealthConnectClient) {
  val granted = healthConnectClient.permissionController.getGrantedPermissions()
  if (granted.containsAll(PERMISSIONS)) {
    // Permissions already granted; proceed with inserting or reading data
  } else {
    requestPermissions.launch(PERMISSIONS)
  }
}

Użytkownicy mogą w dowolnym momencie przyznawać i wycofywać uprawnienia, dlatego aplikacja musi okresowo sprawdzać, czy uprawnienia zostały przyznane, i obsługiwać sytuacje, w których uprawnienia zostaną utracone.

Plany treningowe są powiązane z sesjami ćwiczeń. Dlatego użytkownik musi zezwolić na używanie każdego typu rekordu związanego z planem treningowym, aby w pełni korzystać z tej funkcji Health Connect.

Jeśli na przykład plan treningowy mierzy tętno użytkownika podczas serii biegów, deweloper może musieć zadeklarować te uprawnienia, a użytkownik może musieć je przyznać, aby zapisać sesję ćwiczeń i odczytać wyniki do późniejszej oceny:

  • 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

Często jednak aplikacja, która tworzy plany treningowe i ocenia skuteczność na ich podstawie, nie jest tą samą aplikacją, która korzysta z planów treningowych i zapisuje rzeczywiste dane o ćwiczeniach. W zależności od typu aplikacji nie wszystkie uprawnienia do odczytu i zapisu będą potrzebne. Na przykład w przypadku poszczególnych typów aplikacji możesz potrzebować tylko tych uprawnień:

Aplikacja z planem treningowym Aplikacja do ćwiczeń
WRITE_PLANNED_EXERCISE READ_PLANNED_EXERCISE
READ_EXERCISE WRITE_EXERCISE
READ_EXERCISE_ROUTES WRITE_EXERCISE_ROUTE
READ_HEART_RATE WRITE_HEART_RATE

Informacje zawarte w rekordzie zaplanowanej sesji ćwiczeń

  • Tytuł sesji.
  • Lista zaplanowanych bloków ćwiczeń.
  • czas rozpoczęcia i zakończenia sesji;
  • Typ ćwiczenia.
  • Notatki dotyczące aktywności.
  • Metadane
  • Identyfikator ukończonej sesji ćwiczeń – jest zapisywany automatycznie po ukończeniu sesji ćwiczeń powiązanej z tą zaplanowaną sesją.

Informacje zawarte w rekordzie zaplanowanego bloku ćwiczeń

Zaplanowany blok ćwiczeń zawiera listę kroków ćwiczeń, które umożliwiają powtarzanie różnych grup kroków (np. wykonanie 5 razy z rzędu sekwencji ugięć ramion, burpees i brzuszków).

Informacje zawarte w rekordzie zaplanowanego kroku ćwiczenia

Obsługiwane agregacje

W przypadku tego typu danych nie ma obsługiwanych agregacji.

Przykład użycia

Załóżmy, że użytkownik zaplanował 90-minutowy bieg za 2 dni. Ten bieg będzie obejmować 3 okrążenia wokół jeziora z docelowym tętnem od 90 do 110 uderzeń na minutę.

  1. Zaplanowana sesja ćwiczeń z tymi parametrami jest definiowana przez użytkownika w aplikacji do planowania treningów:
    1. Planowany początek i koniec biegu
    2. Rodzaj ćwiczenia (bieganie)
    3. Liczba okrążeń (powtórzeń)
    4. Docelowe tętno (90–110 uderz./min)
  2. Te informacje są grupowane w bloki ćwiczeń i kroków, a następnie zapisywane w Health Connect przez aplikację planu treningowego jako PlannedExerciseSessionRecord.
  3. Użytkownik przeprowadza zaplanowaną sesję (bieganie).
  4. Dane ćwiczeń związane z sesją są rejestrowane w jeden z tych sposobów:
    1. przez urządzenie do noszenia podczas sesji; Na przykład tętno. Te dane są zapisywane w Health Connect jako typ rekordu aktywności. W tym przypadku HeartRateRecord.
    2. ręcznie przez użytkownika po zakończeniu sesji; Na przykład wskazanie początku i końca rzeczywistego biegu. Te dane są zapisywane w Health Connect jako ExerciseSessionRecord.
  5. Później aplikacja z planem treningowym odczytuje dane z Health Connect, aby ocenić rzeczywiste wyniki w porównaniu z celami wyznaczonymi przez użytkownika w zaplanowanej sesji ćwiczeń.

Planowanie ćwiczeń i wyznaczanie celów

Użytkownik może zaplanować ćwiczenia na przyszłość i ustawić cele. Zapisz to w Health Connect jako zaplanowaną sesję ćwiczeń.

W przykładzie opisanym w sekcji Przykładowe użycie użytkownik planuje 90-minutowy bieg za 2 dni. Ten bieg będzie obejmować 3 okrążenia wokół jeziora z docelowym tętnem od 90 do 110 uderzeń na minutę.

Fragment kodu podobny do tego może znajdować się w programie obsługi formularzy aplikacji, która rejestruje zaplanowane sesje ćwiczeń w Health Connect. Może się też znajdować w punkcie odbioru danych na potrzeby integracji, np. z usługą oferującą szkolenia.

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

val plannedDuration = Duration.ofMinutes(90)
val plannedStartDate = LocalDate.now().plusDays(2)

val plannedExerciseSessionRecord = PlannedExerciseSessionRecord(
    startDate = plannedStartDate,
    duration = plannedDuration,
    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.manualEntry(
      device = Device(type = Device.Companion.TYPE_PHONE)
    )
)
val insertedPlannedExerciseSessions =
    healthConnectClient.insertRecords(listOf(plannedExerciseSessionRecord)).recordIdsList
val insertedPlannedExerciseSessionId = insertedPlannedExerciseSessions.first()

Zapisywanie danych o ćwiczeniach i aktywności

Dwa dni później użytkownik rejestruje rzeczywistą sesję ćwiczeń. Zapisz te dane w Health Connect jako sesję ćwiczeń.

W tym przykładzie czas trwania sesji użytkownika dokładnie odpowiadał zaplanowanemu czasowi trwania.

Poniższy fragment kodu może znajdować się w programie obsługi formularzy aplikacji, która rejestruje sesje ćwiczeń w Health Connect. Może się też znajdować w programach do pozyskiwania i eksportowania danych na urządzeniu do noszenia, które wykrywa i rejestruje sesje ćwiczeń.

insertedPlannedExerciseSessionId jest ponownie używana z poprzedniego przykładu. W prawdziwej aplikacji identyfikator byłby określany przez użytkownika, który wybiera zaplanowaną sesję ćwiczeń z listy istniejących sesji.

// 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.manualEntry(
      device = Device(type = Device.Companion.TYPE_PHONE)
    )
)
val insertedExerciseSessions =
    healthConnectClient.insertRecords(listOf(exerciseSessionRecord))

Urządzenie do noszenia rejestruje też tętno podczas biegu. Poniższy fragment kodu może służyć do generowania rekordów w zakresie docelowym.

W prawdziwej aplikacji główne elementy tego fragmentu kodu mogą znajdować się w procedurze obsługi wiadomości z urządzenia do noszenia, która po zebraniu danych zapisuje pomiary w 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.autoRecorded(
      device = Device(type = Device.Companion.TYPE_WATCH)
    )
)
val insertedHeartRateRecords = healthConnectClient.insertRecords(listOf(heartRateRecord))

Ocena celów skuteczności

Następnego dnia po treningu użytkownika możesz pobrać zarejestrowane ćwiczenie, sprawdzić, czy są jakieś zaplanowane cele ćwiczeń, i ocenić dodatkowe typy danych, aby określić, czy ustawione cele zostały osiągnięte.

Taki fragment kodu można znaleźć w zadaniu okresowym, które służy do oceny celów skuteczności, lub podczas wczytywania listy ćwiczeń i wyświetlania w aplikacji powiadomienia o celach skuteczności.

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