Planes de entrenamiento

Esta guía es compatible con la versión 1.1.0-alpha11 de Health Connect.

Health Connect proporciona un tipo de datos de ejercicio planificado para permitir que las apps de entrenamiento escriban planes de entrenamiento y los lean. Los ejercicios grabados (entrenamientos) se pueden volver a leer para realizar un análisis de rendimiento personalizado que ayude a los usuarios a alcanzar sus objetivos de entrenamiento.

Disponibilidad de funciones

Para determinar si el dispositivo de un usuario admite planes de entrenamiento en Health Connect, verifica la disponibilidad de FEATURE_PLANNED_EXERCISE en el cliente:

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

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

Consulta Cómo verificar la disponibilidad de las funciones para obtener más información.

Permisos necesarios

El acceso a los planes de entrenamiento está protegido por los siguientes permisos:

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

Declara estos permisos en Play Console para tu app, así como en el manifiesto de la app:

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

Eres responsable de declarar todos los permisos adecuados que deseas usar en tus dispositivos y apps. También debes verificar que el usuario haya otorgado cada permiso antes de usarlo.

Los planes de entrenamiento están vinculados a las sesiones de ejercicio. Por lo tanto, el usuario debe dar permiso para usar cada tipo de registro relacionado con un plan de entrenamiento para usar por completo esta función de Health Connect.

Por ejemplo, si un plan de entrenamiento mide la frecuencia cardíaca de un usuario durante una serie de actividades de correr, es posible que el desarrollador deba declarar los siguientes permisos y que el usuario los otorgue para escribir la sesión de ejercicio y leer los resultados para una evaluación posterior:

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

Sin embargo, a menudo, la app que crea planes de entrenamiento y evalúa el rendimiento en función de ellos no es la misma que consume los planes de entrenamiento y escribe los datos de ejercicio reales. Según el tipo de app, no se necesitarán todos los permisos de lectura y escritura. Por ejemplo, es posible que solo necesites estos permisos para cada tipo de app:

App de plan de entrenamiento App de entrenamiento
WRITE_PLANNED_EXERCISE READ_PLANNED_EXERCISE
READ_EXERCISE WRITE_EXERCISE
READ_EXERCISE_ROUTE WRITE_EXERCISE_ROUTE
READ_HEART_RATE WRITE_HEART_RATE

Información que se incluye en un registro de sesión de ejercicio planificada

  • Es el título de la sesión.
  • Una lista de los bloques de ejercicio planificados.
  • Hora de inicio y finalización de la sesión.
  • Tipo de ejercicio.
  • Son las notas de la actividad.
  • Metadatos
  • ID de la sesión de ejercicio completada: Se escribe automáticamente después de que se completa una sesión de ejercicio relacionada con esta sesión de ejercicio planificada.

Información que se incluye en un registro de bloque de ejercicio planificado

Un bloque de ejercicio planificado contiene una lista de pasos de ejercicio para admitir la repetición de diferentes grupos de pasos (por ejemplo, hacer una secuencia de levantamientos de brazos, burpees y abdominales cinco veces seguidas).

Información que se incluye en un registro de pasos de ejercicio planificado

Agregaciones admitidas

No hay agregaciones admitidas para este tipo de datos.

Ejemplo de uso

Supongamos que un usuario planifica una carrera de 90 minutos dentro de dos días. Esta carrera incluirá tres vueltas alrededor de un lago con una frecuencia cardíaca objetivo de entre 90 y 110 lpm.

  1. El usuario define una sesión de ejercicio planificada con lo siguiente en una app de plan de entrenamiento:
    1. Inicio y finalización planificados de la actividad de correr
    2. El tipo de ejercicio (correr)
    3. Cantidad de vueltas (repeticiones)
    4. Objetivo de rendimiento para la frecuencia cardíaca (entre 90 y 110 ppm)
  2. La app del plan de entrenamiento agrupa esta información en bloques de ejercicio y pasos, y la escribe en Health Connect como un PlannedExerciseSessionRecord.
  3. El usuario realiza la sesión planificada (correr).
  4. Los datos de ejercicio relacionados con la sesión se registran de las siguientes maneras:
    1. Por un dispositivo wearable durante la sesión Por ejemplo, la frecuencia cardíaca. Estos datos se escriben en Health Connect como el tipo de registro de la actividad. En este caso, HeartRateRecord.
    2. De forma manual por el usuario después de la sesión Por ejemplo, indicar el inicio y el final de la ejecución real. Estos datos se escriben en Health Connect como un ExerciseSessionRecord.
  5. Más adelante, la app del plan de entrenamiento lee datos de Health Connect para evaluar el rendimiento real en función de los objetivos establecidos por el usuario en la sesión de ejercicio planificada.

Planifica ejercicios y establece objetivos

Un usuario puede planificar su ejercicio en el futuro y establecer objetivos. Escribe esto en Health Connect como una sesión de ejercicio planificada.

En el ejemplo que se describe en Uso de ejemplo, el usuario planifica una carrera de 90 minutos dentro de dos días. Esta carrera tendrá tres vueltas alrededor de un lago con una frecuencia cardíaca objetivo de entre 90 y 110 lpm.

Es posible que encuentres un fragmento como este en el controlador de formularios de una app que registra las sesiones de ejercicio planificadas en Health Connect. También se puede encontrar en el punto de transferencia para integraciones, por ejemplo, con un servicio que ofrece capacitación.

// Ensure 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 = ExerciseSessionRecord.EXERCISE_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
)
val insertedPlannedExerciseSessions =
    healthConnectClient.insertRecords(listOf(plannedExerciseSessionRecord)).recordIdsList
val insertedPlannedExerciseSessionId = insertedPlannedExerciseSessions.first()

Registra datos de ejercicio y actividad

Dos días después, el usuario registra la sesión de ejercicio real. Escribe esto en Health Connect como una sesión de ejercicio.

En este ejemplo, la duración de la sesión del usuario coincidió exactamente con la duración planificada.

Es posible que el siguiente fragmento se encuentre en el controlador de formularios de una app que registra sesiones de ejercicio en Health Connect. También se puede encontrar en los controladores de transferencia y exportación de datos para un wearable capaz de detectar y registrar sesiones de ejercicio.

Aquí, insertedPlannedExerciseSessionId se reutiliza del ejemplo anterior. En una app real, el usuario determinaría el ID seleccionando una sesión de ejercicio planificada de una lista de sesiones existentes.

// Ensure 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,
)
val insertedExerciseSessions =
    healthConnectClient.insertRecords(listOf(exerciseSessionRecord))

Los wearables también registran la frecuencia cardíaca durante la carrera. El siguiente fragmento se podría usar para generar registros dentro del rango objetivo.

En una app real, los elementos principales de este fragmento se pueden encontrar en el controlador de un mensaje de un wearable, que escribiría la medición en Health Connect cuando se recopile.

// Ensure 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,
)
val insertedHeartRateRecords = healthConnectClient.insertRecords(listOf(heartRateRecord))

Evalúa los objetivos de rendimiento

El día después del entrenamiento del usuario, puedes recuperar el ejercicio registrado, verificar si hay objetivos de ejercicio planificados y evaluar tipos de datos adicionales para determinar si se cumplieron los objetivos establecidos.

Es probable que un fragmento como este se encuentre en un trabajo periódico para evaluar los objetivos de rendimiento o cuando se carga una lista de ejercicios y se muestra una notificación sobre los objetivos de rendimiento en una app.

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