Este guia é compatível com a versão 1.1.0-alpha12 ou mais recente do app Conexão Saúde.
A Conexão Saúde oferece um tipo de dados de exercício planejado para que os apps de treino possam gravar planos de treino e os apps de treino possam ler planos de treino. Os exercícios registrados (treinos) podem ser lidos para uma análise de desempenho personalizada que ajuda os usuários a atingir as metas de treino.
Verificar a disponibilidade do app Conexão Saúde
Antes de tentar usar o Conexão Saúde, seu app precisa verificar se ele está disponível
no dispositivo do usuário. A Conexão Saúde pode não estar pré-instalada em todos os dispositivos ou pode estar desativada.
É possível verificar a disponibilidade usando o método
HealthConnectClient.getSdkStatus().
Como verificar a disponibilidade do Conexão Saúde
fun checkHealthConnectAvailability(context: Context) { val providerPackageName = "com.google.android.apps.healthdata" 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 }
Dependendo do status retornado por getSdkStatus(), você pode orientar o usuário
a instalar ou atualizar o Conexão Saúde na Google Play Store, se necessário.
Disponibilidade do recurso
Para determinar se o dispositivo de um usuário é compatível com os planos de treino na Conexão Saúde, verifique a disponibilidade deFEATURE_PLANNED_EXERCISE no cliente:
if (healthConnectClient
.features
.getFeatureStatus(
HealthConnectFeatures.FEATURE_PLANNED_EXERCISE
) == HealthConnectFeatures.FEATURE_STATUS_AVAILABLE) {
// Feature is available
} else {
// Feature isn't available
}
Permissões necessárias
O acesso ao exercício planejado é protegido pelas seguintes permissões:
android.permission.health.READ_PLANNED_EXERCISEandroid.permission.health.WRITE_PLANNED_EXERCISE
Para adicionar o recurso de exercício planejado ao app, solicite
permissões para o tipo de dados PlannedExerciseSession.
Confira a permissão necessária para poder gravar exercícios planejados:
<application>
<uses-permission
android:name="android.permission.health.WRITE_PLANNED_EXERCISE" />
...
</application>
Para ler exercícios planejados, você precisa solicitar as seguintes permissões:
<application>
<uses-permission
android:name="android.permission.health.READ_PLANNED_EXERCISE" />
...
</application>
Solicitar permissões do usuário
Depois de criar uma instância de cliente, seu app precisa solicitar permissões aos usuários. Os usuários precisam concedê-las ou negá-las a qualquer momento. Para fazer isso, crie um conjunto de permissões para os tipos de dados necessários. Verifique se as permissões no conjunto foram declaradas no manifesto do Android primeiro.
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
para verificar se o app já tem as permissões necessárias concedidas. Caso contrário, use
createRequestPermissionResultContract
para solicitá-las. Isso mostra a tela de permissões da Conexão Saúde.
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.") } } }
Permissões relacionadas
Os planos de treinamento estão vinculados a sessões de exercícios. Portanto, o usuário precisa dar permissão para usar cada tipo de registro relacionado a um plano de treino para utilizar esse recurso da Conexão Saúde por completo.
Por exemplo, se um plano de treinamento mede a frequência cardíaca de um usuário durante uma série de corridas, as seguintes permissões podem precisar ser declaradas pelo desenvolvedor e concedidas pelo usuário para gravar a sessão de exercício e ler os resultados para avaliação posterior:
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
No entanto, muitas vezes o app que cria planos de treinamento e avalia o desempenho em relação aos planos não é o mesmo que consome planos de treinamento e grava dados reais de exercícios. Dependendo do tipo de app, nem todas as permissões de leitura e gravação seriam necessárias. Por exemplo, talvez você só precise destas permissões para cada tipo de app:
| App de plano de treino | App de treino |
|---|---|
WRITE_PLANNED_EXERCISE |
READ_PLANNED_EXERCISE |
READ_EXERCISE |
WRITE_EXERCISE |
READ_EXERCISE_ROUTES |
WRITE_EXERCISE_ROUTE |
READ_HEART_RATE |
WRITE_HEART_RATE |
Informações incluídas em um registro de sessão de exercício planejada
- Título da sessão.
- Uma lista de blocos de exercícios planejados.
- Horário de início e término da sessão.
- Tipo de exercício.
- Observações sobre a atividade.
- Metadados.
- ID da sessão de exercício concluída: é gravado automaticamente após a conclusão de uma sessão de exercício relacionada a essa sessão planejada.
Informações incluídas em um registro de bloco de exercícios planejados
Um bloco de exercícios planejados contém uma lista de etapas de exercícios para permitir a repetição de diferentes grupos de etapas (por exemplo, fazer uma sequência de flexões de braço, burpees e abdominais cinco vezes seguidas).
- Descrição do bloco.
- Uma lista de etapas de exercícios planejados.
- Número de repetições.
Informações incluídas em um registro de etapa de exercício planejado
- Descrição da etapa.
- Categoria de exercício.
- Tipo de exercício.
- Uma lista de metas de performance.
- Meta de conclusão.
Agregações compatíveis
Não há agregações compatíveis com esse tipo de dados.
Exemplo de uso
Suponha que um usuário planeje uma corrida de 90 minutos para daqui a dois dias. Essa corrida terá três voltas ao redor de um lago com uma meta de frequência cardíaca entre 90 e 110 bpm.
- Uma sessão de exercícios planejada com os seguintes itens é definida pelo usuário em
um app de plano de treino:
- Início e fim planejados da execução
- O tipo de exercício (corrida)
- Número de voltas (repetições)
- Meta de performance para frequência cardíaca (entre 90 e 110 bpm)
- Essas informações são agrupadas em blocos e etapas de exercício e gravadas
no Conexão Saúde pelo app de plano de treino como um
PlannedExerciseSessionRecord. - O usuário realiza a sessão planejada (corrida).
- Os dados de exercício relacionados à sessão são registrados:
- Por um wearable durante a sessão. Por exemplo, frequência cardíaca.
Esses dados são gravados na Conexão Saúde como o tipo de registro da
atividade. Nesse caso,
HeartRateRecord. - Manualmente pelo usuário após a sessão. Por exemplo, indicando
o início e o fim da execução real. Esses dados são gravados na Conexão Saúde como um
ExerciseSessionRecord.
- Por um wearable durante a sessão. Por exemplo, frequência cardíaca.
Esses dados são gravados na Conexão Saúde como o tipo de registro da
atividade. Nesse caso,
- Depois, o app de plano de treino lê os dados da Conexão Saúde para avaliar o desempenho real em relação às metas definidas pelo usuário na sessão de exercícios planejada.
Planejar exercícios e definir metas
Um usuário pode planejar os exercícios para o futuro e definir metas. Registre isso na Conexão Saúde como uma sessão de exercícios planejada.
No exemplo descrito em Exemplo de uso, o usuário planeja uma corrida de 90 minutos daqui a dois dias. Essa corrida terá três voltas ao redor de um lago com uma meta de frequência cardíaca entre 90 e 110 bpm.
Um snippet como este pode ser encontrado no gerenciador de formulários de um app que registra sessões de exercícios planejadas na Conexão Saúde. Ele também pode ser encontrado no ponto de ingestão para integrações, por exemplo, com um serviço que oferece treinamento.
// 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.activelyRecorded( 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) }
Registrar dados de exercícios e atividades
Dois dias depois, o usuário registra a sessão de exercício real. Grave isso na Conexão Saúde como uma sessão de exercício.
Neste exemplo, a duração da sessão do usuário correspondeu exatamente à duração planejada.
O snippet a seguir pode ser encontrado no gerenciador de formulários de um app que registra sessões de exercício no Conexão Saúde. Ele também pode ser encontrado em gerenciadores de ingestão de dados e exportação para um wearable capaz de detectar e registrar sessões de exercícios.
insertedPlannedExerciseSessionId aqui é reutilizado do exemplo anterior. Em
um app real, o ID seria determinado pelo usuário ao selecionar uma sessão de exercícios
planejada em uma lista de sessões 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.activelyRecorded( device = Device(type = Device.Companion.TYPE_PHONE) ) ) val insertedExerciseSessions = healthConnectClient.insertRecords(listOf(exerciseSessionRecord))
Um wearable também registra a frequência cardíaca durante a corrida. O snippet a seguir pode ser usado para gerar registros dentro do intervalo de destino.
Em um app real, as partes principais desse snippet podem ser encontradas no gerenciador de uma mensagem de um wearable, que grava a medição no Conexão Saúde após a coleta.
// 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.activelyRecorded( device = Device(type = Device.Companion.TYPE_WATCH) ) ) val insertedHeartRateRecords = healthConnectClient.insertRecords(listOf(heartRateRecord))
Avaliar metas de performance
No dia seguinte ao treino do usuário, é possível recuperar o exercício registrado, verificar as metas de exercício planejadas e avaliar outros tipos de dados para determinar se as metas definidas foram atingidas.
Um snippet como esse provavelmente seria encontrado em um job periódico para avaliar metas de performance ou ao carregar uma lista de exercícios e mostrar uma notificação sobre metas de performance em um 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 } } } } } } } }
Sessões de exercícios
As sessões de exercícios podem incluir várias atividades, de corrida a badminton.
Gravar sessões de exercícios
Confira como criar uma solicitação de inserção que inclui uma sessão:
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
)
)
}
Ler uma sessão de exercícios
Confira um exemplo de como ler uma sessão de exercícios:
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
}
}
Gravar dados de subtipo
As sessões também podem ser compostas por dados de subtipo opcionais, que fornecem mais informações para melhorar a sessão.
Por exemplo, as sessões de exercício podem incluir as classes ExerciseSegment, ExerciseLap
e 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()
)