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 que las apps de entrenamiento lean planes de entrenamiento. Los ejercicios registrados (entrenamientos) se pueden leer para realizar análisis de rendimiento personalizados que ayuden 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 funciones para obtener más información.

Permisos necesarios

El acceso al ejercicio planificado está protegido por los siguientes permisos:

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

Si deseas agregar la función de ejercicio planificado a tu app, comienza por solicitar permisos de escritura para el tipo de datos PlannedExerciseSession.

Este es el permiso que debes declarar para poder escribir un ejercicio planificado:

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

Para leer el ejercicio planificado, debes solicitar los siguientes permisos:

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

Solicita permisos al usuario

Después de crear una instancia de cliente, tu app debe solicitarle permisos al usuario. Los usuarios deben poder otorgar o rechazar permisos en cualquier momento.

Para hacerlo, crea un conjunto de permisos para los tipos de datos necesarios. Primero, asegúrate de que los permisos del conjunto se declaren en tu manifiesto de Android.

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

Usa getGrantedPermissions para ver si tu app ya tiene otorgados los permisos necesarios. De lo contrario, usa createRequestPermissionResultContract para solicitarlos. Se mostrará la pantalla de permisos de 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)
  }
}

Como los usuarios pueden otorgar o revocar permisos en cualquier momento, tu app necesita verificar en forma periódica los permisos otorgados y controlar situaciones en las que el permiso se pierde.

Los planes de entrenamiento están vinculados a las sesiones de ejercicio. Por lo tanto, el usuario debe otorgar permiso para usar cada tipo de registro relacionado con un plan de entrenamiento para aprovechar al máximo 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 carreras, 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 su posterior evaluación:

  • 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

Sin embargo, a menudo, la app que crea planes de entrenamiento y evalúa el rendimiento en función de los planes no es la misma que la app que consume planes de entrenamiento y escribe datos de ejercicio reales. Según el tipo de app, no se necesitarían 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_ROUTES WRITE_EXERCISE_ROUTE
READ_HEART_RATE WRITE_HEART_RATE

Información incluida en un registro de sesión de ejercicio planificada

  • Es el título de la sesión.
  • Es una lista de bloques de ejercicios planificados.
  • Hora de inicio y finalización de la sesión.
  • Tipo de ejercicio.
  • Notas sobre 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 incluida 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 flexiones de bíceps, burpees y abdominales cinco veces seguidas).

Información incluida en un registro de paso de ejercicio planificado

Agregaciones admitidas

No hay agregaciones admitidas para este tipo de datos.

Ejemplo de uso

Supongamos que un usuario planea 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 ejecución
    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. Esta información se agrupa en bloques y pasos de ejercicio, y la app del plan de entrenamiento la escribe en Health Connect como un objeto PlannedExerciseSessionRecord.
  3. El usuario realiza la sesión planificada (correr).
  4. Los datos de ejercicio relacionados con la sesión se registran de una de las siguientes maneras:
    1. Por un 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. El usuario lo hace de forma manual 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 comparación con 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 Ejemplo de uso, el usuario planifica una carrera de 90 minutos para 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.

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

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

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 importación 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 ID se determinaría cuando el usuario seleccione una sesión de ejercicio planificada de una lista de sesiones existentes.

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

Un wearable también registra su frecuencia cardíaca durante la carrera. El siguiente fragmento se podría usar para generar registros dentro del rango objetivo.

En una app real, las partes principales de este fragmento se pueden encontrar en el controlador de un mensaje de un dispositivo wearable, que escribiría la medición en Health Connect después de la recopilación.

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

Evalúa los objetivos de rendimiento

Al día siguiente 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.

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

Sesiones de ejercicio

Las sesiones de ejercicio pueden incluir cualquier actividad, desde correr hasta jugar bádminton.

Cómo escribir sesiones de ejercicio

A continuación, se muestra cómo compilar una solicitud de inserción que incluya una sesión:

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"
            ),
            // ... other records
        )
    )
}

Cómo leer una sesión de ejercicio

Este es un ejemplo de cómo leer una sesión de ejercicio:

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

Cómo escribir datos de subtipos

Las sesiones también pueden estar compuestas por datos de subtipo opcionales, que enriquecen la sesión con información adicional.

Por ejemplo, las sesiones de ejercicio pueden incluir las clases ExerciseSegment, ExerciseLap y 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
)