Desenvolver experiências de treino com a Conexão Saúde

Se você quiser criar uma experiência de treino no seu app, use o Conexão Saúde para fazer o seguinte:

  • Gravar sessões de exercícios
  • Gravar trajetos de treino
  • Gravar métricas de treino, como frequência cardíaca, velocidade e distância
  • Ler dados de treino de outros apps

Este guia descreve como criar esses recursos de treino, abordando tipos de dados, execução em segundo plano, permissões, fluxos de trabalho recomendados e práticas recomendadas.

Visão geral: como criar um rastreador de treino abrangente

Para criar uma experiência abrangente de rastreamento de treino usando o Conexão Saúde, siga estas etapas principais:

  • Implementar corretamente as permissões com base nas permissões de saúde.
  • Gravar sessões usando ExerciseSessionRecord.
  • Gravar dados de treino de forma consistente durante a sessão.
  • Gerenciar a execução em segundo plano corretamente para verificar a captura contínua de dados.
  • Ler dados de sessão para resumos e análises pós-treino.

Esse fluxo de trabalho permite a interoperabilidade com outros apps do Conexão Saúde e verifica o acesso aos dados controlado pelo usuário.

Antes de começar

Antes de implementar recursos de treino:

Principais conceitos

O Conexão Saúde representa dados de treino usando alguns componentes principais. Um ExerciseSessionRecord atua como o registro central de um treino, contendo detalhes como horários de início ou término e tipo de exercício. Durante uma sessão, vários tipos de dados, como HeartRateRecord ou SpeedRecord, podem ser gravados. Para atividades ao ar livre, ExerciseRoute armazena dados de GPS, que são vinculados à sessão correspondente.

Sessões de exercícios

ExerciseSessionRecord é o registro central dos dados de treino, representando uma única sessão de treino. Cada registro armazena:

  • startTime
  • endTime
  • exerciseType
  • Metadados de sessão opcionais (título, observações)

Um ExerciseSessionRecord também pode conter trajetos, voltas e segmentos de exercícios como parte dos dados. Além disso, outros tipos de dados, como HeartRateRecord ou SpeedRecord, podem ser gravados durante uma sessão e associados a ela.

Tipos de dados associados

Os dados associados a sessões de treino são representados por tipos de registro individuais. Os tipos comuns incluem:

Para uma lista completa de tipos de dados, consulte Tipos de dados do Conexão Saúde.

Trajetos de exercícios

É possível associar um trajeto a treinos ao ar livre usando ExerciseRoute. Os trajetos consistem em objetos ExerciseRoute.Location sequenciais, cada um contendo:

  • Latitude e longitude
  • Altitude opcional
  • Rolamento opcional
  • Informações de precisão
  • Carimbo de data/hora

Vincular trajetos de sessão

Um ExerciseRoute contém os dados de localização sequenciais de uma sessão de exercícios. Ele não é tratado como um registro independente no Conexão Saúde. Em vez disso, você fornece dados ExerciseRoute ao inserir ou atualizar um ExerciseSessionRecord.

Considerações de desenvolvimento

Os apps de rastreamento de treino geralmente precisam ser executados por períodos prolongados, com frequência em segundo plano quando a tela está desligada. Ao criar recursos de treino, é importante considerar como gerenciar a execução em segundo plano e solicitar as permissões necessárias para dados de treino.

Execução em segundo plano

Os apps de treino geralmente são executados com a tela desligada. Nesse estado, use:

  • Serviços em primeiro plano para amostragem de localização e sensor
  • WorkManager para gravação ou sincronização adiada
  • Estratégias de lote para gravação de registros regulares

Mantenha a continuidade mantendo o ID da sessão consistente em todas as gravações.

Permissões

O app precisa solicitar as permissões relevantes do Conexão Saúde antes de ler ou gravar dados de treino. As permissões comuns para treinos incluem sessões de exercícios, trajetos de exercícios e métricas como frequência cardíaca ou velocidade. Isso inclui o seguinte:

  • Sessões de exercícios:permissões de leitura e gravação para ExerciseSessionRecord.
  • Trajetos de exercícios:permissões de leitura e gravação para ExerciseRoute.
  • Frequência cardíaca:permissões de leitura e gravação para HeartRateRecord.
  • Velocidade:permissões de leitura e gravação para SpeedRecord.
  • Distância:permissões de leitura e gravação para DistanceRecord.
  • Calorias:permissões de leitura e gravação para TotalCaloriesBurnedRecord.
  • Elevação ganha:permissões de leitura e gravação para ElevationGainedRecord.
  • Cadência de passos:permissões de leitura e gravação para StepsCadenceRecord.
  • Potência:permissões de leitura e gravação para PowerRecord.
  • Passos:permissões de leitura e gravação para StepsRecord.

O exemplo a seguir mostra como solicitar várias permissões para uma sessão de treino que inclui dados de trajeto, frequência cardíaca, distância, calorias, velocidade e passos:

Depois de criar uma instância de cliente, seu app precisa solicitar permissões aos usuários. Os usuários precisam poder conceder ou negar permissões 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 primeiro no manifesto do Android.

// Create a set of permissions for required data types
val PERMISSIONS =
    setOf(
  HealthPermission.getReadPermission(ExerciseSessionRecord::class),
  HealthPermission.getWritePermission(ExerciseSessionRecord::class),
  HealthPermission.getReadPermission(ExerciseRoute::class),
  HealthPermission.getWritePermission(ExerciseRoute::class),
  HealthPermission.getReadPermission(HeartRateRecord::class),
  HealthPermission.getWritePermission(HeartRateRecord::class),
  HealthPermission.getReadPermission(SpeedRecord::class),
  HealthPermission.getWritePermission(SpeedRecord::class),
  HealthPermission.getReadPermission(DistanceRecord::class),
  HealthPermission.getWritePermission(DistanceRecord::class),
  HealthPermission.getReadPermission(TotalCaloriesBurnedRecord::class),
  HealthPermission.getWritePermission(TotalCaloriesBurnedRecord::class),
  HealthPermission.getReadPermission(StepsRecord::class),
  HealthPermission.getWritePermission(StepsRecord::class)
)

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

// 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 os usuários podem conceder ou revogar permissões a qualquer momento, seu app precisa verificar as permissões sempre antes de usá-las e lidar com cenários em que a permissão é perdida.

Para solicitar permissões, chame a função checkPermissionsAndRun:

if (!granted.containsAll(PERMISSIONS)) {
    requestPermissions.launch(PERMISSIONS)
    // Check if required permissions are not granted, and return
  }
// Permissions already granted; proceed with inserting or reading data

Se você precisar solicitar permissões apenas para um único tipo de dados, como frequência cardíaca, inclua apenas esse tipo de dados no conjunto de permissões:

O acesso à frequência cardíaca é protegido pelas seguintes permissões:

  • android.permission.health.READ_HEART_RATE
  • android.permission.health.WRITE_HEART_RATE

Para adicionar a capacidade de frequência cardíaca ao app, comece solicitando permissões para o tipo de dados HeartRateRecord.

Confira a permissão necessária para poder gravar a frequência cardíaca:

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

Para ler a frequência cardíaca, é necessário solicitar as seguintes permissões:

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

Implementar uma sessão de treino

Esta seção descreve o fluxo de trabalho recomendado para gravar dados de treino.

Iniciar a sessão

Para criar um novo treino:

  1. Gere um ID de sessão exclusivo: verifique se esse ID é estável. Se o processo do app for encerrado e reiniciado, você precisará retomar o uso do mesmo ID para evitar sessões fragmentadas.
  2. Defina um metadata.clientRecordId para evitar duplicados durante novas tentativas de sincronização.
  3. Grave um ExerciseSessionRecord: inclua o horário de início.
  4. Comece a coletar dados de tipo de dados e GPS: só inicie esses dados depois que o registro da sessão for inicializado.

Exemplo:

val sessionId = UUID.randomUUID().toString()
val sessionClientId = UUID.randomUUID().toString()

val session = ExerciseSessionRecord(
    id = sessionId,
    exerciseType = ExerciseType.EXERCISE_TYPE_RUNNING,
    startTime = Instant.now(),
    endTime = null,
    metadata = Metadata(clientRecordId = sessionClientId),
)

healthConnectClient.insertRecords(listOf(session))

Gravar trajetos de exercícios

Para saber mais sobre a leitura de orientações, consulte Ler dados brutos.

Ao gravar um trajeto de exercício, você precisa agrupar os dados. Isso significa que, em vez de salvar cada ponto de GPS individualmente, você coleta um grupo de pontos e salva todos de uma só vez em uma única chamada.

Isso é importante porque, sempre que o app lê ou grava no Conexão Saúde, ele usa um pouco de bateria e poder de processamento.

O código a seguir mostra como gravar em lotes:

// 1. Create a list to hold your route locations
val routeLocations = mutableListOf<ExerciseRoute.Location>()

// 2. Add points to your list as the exercise happens
routeLocations.add(
    ExerciseRoute.Location(
        time = Instant.now(),
        latitude = 37.7749,
        longitude = -122.4194
    )
)

// ... keep adding points over a period of time ...

// 3. Save the whole list at once (Batching)
val session = ExerciseSessionRecord(
    startTime = startTime,
    endTime = endTime,
    exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_RUNNING,
    // We pass the whole list here
    exerciseRoute = ExerciseRoute(routeLocations)
)

healthConnectClient.insertRecords(listOf(session))

Encerrar uma sessão

Depois de interromper a coleta de dados:

  • Atualize o registro: o app atualiza ExerciseSessionRecord com um endTime.
  • Finalize os dados: opcionalmente, calcule os valores de resumo (como distância total ou ritmo médio) e grave-os como registros adicionais.
val finishedSession = session.copy(endTime = Instant.now())
healthConnectClient.updateRecords(listOf(finishedSession))

Ler dados de treino

Os apps podem ler sessões de exercícios e os dados associados para resumir a atividade, fornecer insights de saúde ou sincronizar dados com um servidor externo. Por exemplo, é possível ler um ExerciseSessionRecord e consultar o HeartRateRecord ou DistanceRecord que ocorreu durante o mesmo intervalo de tempo.

Se você precisar sincronizar dados de treino com um servidor de back-end ou manter o repositório de dados do app atualizado com o Conexão Saúde, use ChangeLogs. Isso permite recuperar uma lista de registros inseridos, atualizados ou excluídos desde um ponto específico no tempo, o que é mais eficiente do que rastrear manualmente as mudanças ou ler repetidamente todos os dados. Para mais informações, consulte Sincronizar dados com o Conexão Saúde.

Ler sessões

Para ler sessões de exercícios, use um ReadRecordsRequest com ExerciseSessionRecord como o tipo. Geralmente, você filtra isso por um intervalo de tempo específico.

suspend fun readExerciseSessions(
    healthConnectClient: HealthConnectClient,
    startTime: Instant,
    endTime: Instant
) {
    val response = healthConnectClient.readRecords(
        ReadRecordsRequest(
            recordType = ExerciseSessionRecord::class,
            timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
        )
    )

    for (exerciseRecord in response.records) {
        // Process each session
        val exerciseType = exerciseRecord.exerciseType
        val notes = exerciseRecord.notes
    }
}

Ler trajetos

Embora os dados ExerciseRoute sejam gravados como parte de uma sessão de exercícios, eles precisam ser lidos separadamente. Use o método getExerciseRoute() com o ID da sessão para ler os dados do trajeto:

suspend fun readExerciseRoute(
    healthConnectClient: HealthConnectClient,
    exerciseSessionRecord: ExerciseSessionRecord
) {
    // Check if the session has a route
    val route = healthConnectClient.getExerciseRoute(
        exerciseSessionRecordId = exerciseSessionRecord.metadata.id
    )

    when (route) {
        is ExerciseRouteResponse.Success -> {
            val locations = route.exerciseRoute.locations
            for (location in locations) {
                // Use latitude, longitude, and altitude
            }
        }
        is ExerciseRouteResponse.NoData -> {
            // Handle case where no route exists
        }
        is ExerciseRouteResponse.ConsentRequired -> {
            // Handle case where permissions are missing
        }
    }
}

Ler tipos de dados

Para ler dados granulares específicos (como frequência cardíaca) que ocorreram durante uma sessão, use startTime e endTime da sessão para filtrar a solicitação desse tipo de dados.

suspend fun readHeartRateData(
    healthConnectClient: HealthConnectClient,
    exerciseSession: ExerciseSessionRecord
) {
    val response = healthConnectClient.readRecords(
        ReadRecordsRequest(
            recordType = HeartRateRecord::class,
            timeRangeFilter = TimeRangeFilter.between(
                exerciseSession.startTime,
                exerciseSession.endTime
            )
        )
    )

    for (heartRateRecord in response.records) {
        for (sample in heartRateRecord.samples) {
            val bpm = sample.beatsPerMinute
        }
    }
}

Práticas recomendadas

Siga estas diretrizes para melhorar a confiabilidade dos dados e a experiência do usuário:

  • Grave com frequência durante o rastreamento ativo: para o rastreamento ativo, grave os dados assim que eles ficarem disponíveis ou em um intervalo máximo de 15 minutos.
  • Use o WorkManager para sincronizações em segundo plano: use WorkManager para gravações adiadas. Procure um intervalo de 15 minutos para encontrar um equilíbrio entre dados em tempo real e eficiência da bateria.
  • Solicitações de gravação em lote: não grave cada evento de sensor individualmente. Divida as solicitações. O Conexão Saúde processa até 1.000 registros por solicitação de gravação.
  • Mantenha os IDs de sessão estáveis e exclusivos: use identificadores consistentes para suas sessões. Se uma sessão for editada ou atualizada, o uso do mesmo ID impedirá que ela seja tratada como uma sessão nova e separada.
  • Use o lote para tipos de dados e pontos de trajeto: para reduzir a sobrecarga de entrada/saída e preservar a duração da bateria, agrupe os pontos de dados em uma única chamada insertRecords em vez de gravar cada ponto individualmente.
  • Evite gravar dados duplicados: use IDs de cliente: ao criar registros, defina um metadata.clientRecordId. O Conexão Saúde usa isso para identificar registros exclusivos. Se você tentar gravar um registro com um clientRecordId que já existe, o Conexão Saúde vai ignorar o duplicado ou atualizar o registro atual em vez de criar um novo. Definir um metadata.clientRecordId é a maneira mais eficaz de evitar duplicados durante novas tentativas de sincronização ou reinstalações de apps.
    val record = StepsRecord(
        count = 100,
        startTime = startTime,
        endTime = endTime,
        startZoneOffset = ZoneOffset.UTC,
        endZoneOffset = ZoneOffset.UTC,
        metadata = Metadata(
            // Use a unique ID from your own database
            clientRecordId = "daily_steps_2023_10_27_user_123"
        )
    )
  • Verifique os dados atuais: antes de sincronizar, consulte o intervalo de tempo para verificar se os registros do seu app já existem.
  • Validar a precisão do GPS: filtre amostras de GPS de baixa precisão (por exemplo, pontos com um raio de precisão horizontal alto) antes de gravar no ExerciseRoute para verificar se o mapa parece limpo e profissional.
  • Verifique se os carimbos de data/hora não se sobrepõem: verifique se uma nova sessão não começa antes do término da anterior. Sessões sobrepostas podem causar conflitos em painéis de fitness e cálculos de resumo.
  • Forneça justificativas claras para a permissão: use o fluxo Permission.createIntent para explicar por que o app precisa de acesso a dados de saúde, por exemplo: "Para monitorar as tendências de pressão arterial e fornecer insights".
  • Ofereça suporte a pausar e retomar: verifique se o app processa pausas corretamente. Quando um usuário pausa, interrompa a coleta de pontos de trajeto e tipos de dados para que o ritmo e a duração médios permaneçam precisos.
  • Teste sessões de longa duração: monitore o consumo de bateria durante sessões que duram várias horas para verificar se o intervalo de lote e o uso do sensor não esgotam o dispositivo.
  • Alinhe os carimbos de data/hora com as taxas de sensor: combine os carimbos de data/hora do registro com a frequência real dos sensores para manter a alta fidelidade dos dados.

Teste

Para verificar a correção dos dados e uma experiência do usuário de alta qualidade, siga estas estratégias de teste e consulte a documentação oficial de casos de uso principais de teste.

Ferramentas de verificação

  • Caixa de ferramentas do Conexão Saúde: use esse app complementar para inspecionar registros manualmente, excluir dados de teste e simular mudanças no banco de dados. Essa é a melhor maneira de verificar se os registros estão sendo armazenados corretamente.
  • Teste de unidade com FakeHealthConnectClient: use a biblioteca de testes para verificar como o app processa casos extremos, como revogação de permissão ou exceções de API, sem precisar de um dispositivo físico.

Checklist de qualidade

Arquitetura típica

Uma implementação de treino geralmente inclui:

Componente Gerencia
Controlador de sessão Estado da sessão
Temporizador
Lógica de lote
Controladores de tipos de dados
Amostragem de localização
Camada de repositório (encapsula operações do Conexão Saúde): Inserir sessão
Inserir tipos de dados
Insere pontos de trajeto
Ler resumos de sessão
Camada da interface (mostra): Duração
Tipos de dados ativos
Visualização do mapa
Cálculos divididos
Rastro de GPS ativo

Solução de problemas

Sintoma Possível causa Resolução
Trajeto não associado à sessão ID da sessão ou intervalo de tempo incompatível. Verifique se o ExerciseRoute está gravado com um intervalo de tempo que se enquadra totalmente na duração do ExerciseSessionRecord. Verifique se você está usando IDs consistentes ao referenciar a sessão mais tarde. Consulte Gravar trajetos de exercícios.
Tipos de dados ausentes (por exemplo, frequência cardíaca) Permissões de gravação ausentes ou filtros de tempo incorretos. Verifique se você solicitou e o usuário concedeu a permissão de tipo de dados específica. Verifique se o ReadRecordsRequest usa um TimeRangeFilter que corresponde à sessão. Consulte Permissões.
Falha na gravação da sessão Carimbos de data/hora sobrepostos. O Conexão Saúde pode rejeitar registros que se sobrepõem aos dados atuais do mesmo app. Valide se o startTime de uma nova sessão é posterior ao endTime da anterior.
Nenhum dado de GPS gravado O serviço em primeiro plano foi encerrado ou está inativo. Para coletar dados enquanto a tela está desligada, é necessário usar um serviço em primeiro plano com o foregroundServiceType="health" ou de localização.
Registros duplicados aparecem clientRecordId ausente. Atribua um clientRecordId exclusivo nos Metadata de cada registro. Isso permite que o Conexão Saúde execute a desduplicação se os mesmos dados forem gravados duas vezes durante uma nova tentativa de sincronização. Consulte as práticas recomendadas.

Etapas comuns de depuração

Verifique o estado da permissão. Sempre chame getPermissionStatus() antes de tentar uma operação de leitura ou gravação. Os usuários podem revogar permissões nas configurações do sistema a qualquer momento.
Verifique o modo de execução. Se o app não estiver coletando dados em segundo plano, verifique se você declarou as permissões corretas no arquivo AndroidManifest.xml e se o usuário não colocou o app no modo "Restrição de bateria".