Développer des expériences d'entraînement avec Santé Connect

Si vous souhaitez créer une expérience d'entraînement dans votre application, vous pouvez utiliser Santé Connect pour effectuer les opérations suivantes :

  • Écrire des sessions d'exercice
  • Écrire des parcours d'entraînement
  • Écrire des métriques d'entraînement telles que la fréquence cardiaque, la vitesse et la distance
  • Lire les données d'entraînement d'autres applications

Ce guide explique comment créer ces fonctionnalités d'entraînement, en abordant les types de données, l'exécution en arrière-plan, les autorisations, les workflows recommandés et les bonnes pratiques.

Présentation : Créer un suivi d'entraînement complet

Vous pouvez créer une expérience de suivi d'entraînement complète à l'aide de Santé Connect en procédant comme suit :

  • Implémenter correctement les autorisations en fonction des autorisations de santé.
  • Enregistrer des sessions à l'aide de ExerciseSessionRecord.
  • Écrire des données d'entraînement de manière cohérente pendant la session.
  • Gérer correctement l'exécution en arrière-plan pour vérifier la capture continue des données.
  • Lire les données de session pour les récapitulatifs et les analyses après l'entraînement.

Ce workflow permet l'interopérabilité avec d'autres applications Santé Connect et vérifie l'accès aux données contrôlé par l'utilisateur.

Avant de commencer

Avant d'implémenter des fonctionnalités d'entraînement :

Concepts clés

Santé Connect représente les données d'entraînement à l'aide de quelques composants de base. Un ExerciseSessionRecord sert d'enregistrement central pour un entraînement, contenant des informations telles que les heures de début et de fin, ainsi que le type d'exercice. Pendant une session, différents types de données tels que HeartRateRecord ou SpeedRecord peuvent être enregistrés. Pour les activités en extérieur, ExerciseRoute stocke les données GPS, qui sont liées à la session correspondante.

Sessions d'exercice

ExerciseSessionRecord est l'enregistrement central des données d'entraînement, représentant une seule session d'entraînement. Chaque enregistrement stocke les éléments suivants :

  • startTime
  • endTime
  • exerciseType
  • Métadonnées de session facultatives (titre, notes)

Un ExerciseSessionRecord peut également contenir des parcours d'exercice, des tours et des segments dans ses données. En outre, d'autres types de données tels que HeartRateRecord ou SpeedRecord peuvent être enregistrés pendant une session et y être associés.

Types de données associés

Les données associées aux sessions d'entraînement sont représentées par des types d'enregistrement individuels. Les types courants incluent :

  • HeartRateRecord: représente une série de mesures de la fréquence cardiaque.
  • SpeedRecord: représente une série de mesures de la vitesse.
  • DistanceRecord: représente la distance parcourue entre les lectures.
  • TotalCaloriesBurnedRecord: représente le nombre total de calories brûlées entre les lectures.
  • ElevationGainedRecord: représente le dénivelé positif entre les lectures.
  • StepsCadenceRecord: représente la cadence des pas entre les lectures.
  • PowerRecord: représente la puissance de sortie entre les lectures, ce qui est courant dans les activités comme le cyclisme.

Pour obtenir la liste complète des types de données, consultez Types de données Santé Connect.

Itinéraires d'exercice

Vous pouvez associer un itinéraire à des entraînements en extérieur à l'aide d'ExerciseRoute. Les itinéraires sont constitués d'objets ExerciseRoute.Location séquentiels, chacun contenant les éléments suivants :

  • Latitude et longitude
  • Altitude facultative
  • Cap facultatif
  • Informations sur la précision
  • Code temporel

Lier des itinéraires de session

Un ExerciseRoute contient les données de localisation séquentielles d'une session d'exercice. Il n'est pas traité comme un enregistrement indépendant dans Santé Connect. Au lieu de cela, vous fournissez des données ExerciseRoute lors de l'insertion ou de la mise à jour d'un ExerciseSessionRecord.

Considérations relatives au développement

Les applications de suivi d'entraînement doivent souvent s'exécuter pendant de longues périodes, fréquemment en arrière-plan lorsque l'écran est éteint. Lorsque vous créez vos fonctionnalités d'entraînement, il est important de réfléchir à la manière de gérer l'exécution en arrière-plan et de demander les autorisations nécessaires pour les données d'entraînement.

Exécution en arrière-plan

Les applications d'entraînement s'exécutent généralement lorsque l'écran est éteint. Dans cet état, vous devez utiliser :

  • Services de premier plan pour l'échantillonnage de la localisation et des capteurs
  • WorkManager pour l'écriture ou la synchronisation différée
  • Stratégies de traitement par lot pour l'écriture régulière d'enregistrements

Maintenez la continuité en conservant le même ID de session pour toutes les écritures.

Autorisations

Votre application doit demander les autorisations Santé Connect appropriées avant de lire ou d'écrire des données d'entraînement. Les autorisations courantes pour les entraînements incluent les sessions d'exercice, les itinéraires d'exercice et les métriques telles que la fréquence cardiaque ou la vitesse. et vous devriez pouvoir :

  • Sessions d'exercice : autorisations de lecture et d'écriture pour ExerciseSessionRecord.
  • Itinéraires d'exercice : autorisations de lecture et d'écriture pour ExerciseRoute.
  • Fréquence cardiaque : autorisations de lecture et d'écriture pour HeartRateRecord.
  • Vitesse : autorisations de lecture et d'écriture pour SpeedRecord.
  • Distance : autorisations de lecture et d'écriture pour DistanceRecord.
  • Calories : autorisations de lecture et d'écriture pour TotalCaloriesBurnedRecord.
  • Dénivelé positif : autorisations de lecture et d'écriture pour ElevationGainedRecord.
  • Cadence des pas : autorisations de lecture et d'écriture pour StepsCadenceRecord.
  • Puissance : autorisations de lecture et d'écriture pour PowerRecord.
  • Pas : autorisations de lecture et d'écriture pour StepsRecord.

L'exemple suivant montre comment demander plusieurs autorisations pour une session d'entraînement incluant des données d'itinéraire, de fréquence cardiaque, de distance, de calories, de vitesse et de pas :

Après avoir créé une instance de client, votre application doit demander des autorisations à l'utilisateur. Les utilisateurs doivent être autorisés à accorder ou à refuser des autorisations à tout moment.

Pour ce faire, créez un ensemble d'autorisations pour les types de données requis. Assurez-vous d'abord que les autorisations de l'ensemble sont déclarées dans votre fichier manifeste 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)
)

Utilisez getGrantedPermissions pour voir si votre application dispose déjà des autorisations requises accordées. Si ce n'est pas le cas, utilisez createRequestPermissionResultContract pour demander ces autorisations. L'écran des autorisations de Santé Connect s'affiche.

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

Étant donné que les utilisateurs peuvent accorder ou révoquer des autorisations à tout moment, votre application doit vérifier les autorisations chaque fois avant de les utiliser et gérer les scénarios dans lesquels l'autorisation est perdue.

Pour demander des autorisations, appelez la fonction 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

Si vous n'avez besoin de demander des autorisations que pour un seul type de données, tel que la fréquence cardiaque, n'incluez que ce type de données dans votre ensemble d'autorisations :

L'accès à la fréquence cardiaque est protégé par les autorisations suivantes :

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

Pour ajouter la fonctionnalité de fréquence cardiaque à votre application, commencez par demander des autorisations pour le type de données HeartRateRecord.

Voici l'autorisation que vous devez déclarer pour pouvoir écrire la fréquence cardiaque :

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

Pour lire la fréquence cardiaque, vous devez demander les autorisations suivantes :

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

Implémenter une session d'entraînement

Cette section décrit le workflow recommandé pour enregistrer les données d'entraînement.

Lancer une session

Pour créer un entraînement :

  1. Générez un ID de session unique : vérifiez que cet ID est stable. Si le processus de votre application est arrêté et redémarré, vous devez pouvoir reprendre l'utilisation du même ID pour éviter les sessions fragmentées.
  2. Définissez un metadata.clientRecordId pour éviter les doublons lors des nouvelles tentatives de synchronisation.
  3. Écrivez un ExerciseSessionRecord : incluez l'heure de début.
  4. Commencez à collecter des données de type et des données GPS : ne démarrez ces opérations qu'une fois l'enregistrement de la session correctement initialisé.

Exemple :

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

Enregistrer des itinéraires d'exercice

Pour en savoir plus sur la lecture des conseils, consultez Lire les données brutes.

Lorsque vous enregistrez un itinéraire d'exercice, vous devez regrouper vos données. Cela signifie qu'au lieu d'enregistrer chaque point GPS individuel au fur et à mesure, vous collectez un groupe de points et les enregistrez tous en une seule fois lors d'un seul appel.

Ceci est important, car chaque fois que votre application lit ou écrit dans Santé Connect, elle utilise un peu de batterie et de puissance de traitement.

Le code suivant montre comment enregistrer par lot :

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

Mettre fin à une session

Après avoir arrêté la collecte de données :

  • Mettez à jour l'enregistrement : votre application met à jour ExerciseSessionRecord avec une endTime.
  • Finalisez les données : vous pouvez calculer des valeurs récapitulatives (comme la distance totale ou le rythme moyen) et les écrire en tant qu'enregistrements supplémentaires.
val finishedSession = session.copy(endTime = Instant.now())
healthConnectClient.updateRecords(listOf(finishedSession))

Lire les données d'entraînement

Les applications peuvent lire les sessions d'exercice et les données associées pour résumer l'activité, fournir des insights sur la santé ou synchroniser les données avec un serveur externe. Par exemple, vous pouvez lire un ExerciseSessionRecord, puis interroger le HeartRateRecord ou le DistanceRecord qui s'est produit pendant le même intervalle de temps.

Si vous devez synchroniser les données d'entraînement avec un serveur backend ou maintenir le datastore de votre application à jour avec Santé Connect, utilisez ChangeLogs. Cela vous permet de récupérer une liste d'enregistrements insérés, mis à jour ou supprimés depuis un moment précis, ce qui est plus efficace que de suivre manuellement les modifications ou de lire toutes les données de manière répétée. Pour en savoir plus, consultez Synchroniser des données avec Santé Connect.

Lire des sessions

Pour lire des sessions d'exercice, utilisez un ReadRecordsRequest avec ExerciseSessionRecord comme type. Vous filtrez généralement cette requête par une plage de temps spécifique.

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

Lire des itinéraires

Bien que les données ExerciseRoute soient écrites dans le cadre d'une session d'exercice, elles doivent être lues séparément. Utilisez la méthode getExerciseRoute() avec l'ID de la session pour lire ses données d'itinéraire :

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

Lire des types de données

Pour lire des données granulaires spécifiques (comme la fréquence cardiaque) qui se sont produites pendant une session, utilisez la startTime et l'endTime de la session pour filtrer la requête pour ce type de données.

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

Bonnes pratiques

Suivez ces consignes pour améliorer la fiabilité des données et l'expérience utilisateur :

  • Écrivez fréquemment pendant le suivi actif : pour le suivi actif, écrivez les données dès qu'elles sont disponibles ou à un intervalle maximal de 15 minutes.
  • Utilisez WorkManager pour les synchronisations en arrière-plan : utilisez WorkManager pour les écritures différées. Visez un intervalle de 15 minutes pour trouver un équilibre entre les données en temps réel et l'efficacité de la batterie.
  • Regroupez les requêtes d'écriture : n'écrivez pas chaque événement de capteur individuellement. Regroupez vos requêtes. Santé Connect gère jusqu'à 1 000 enregistrements par requête d'écriture.
  • Conservez des ID de session stables et uniques : utilisez des identifiants cohérents pour vos sessions. Si une session est modifiée ou mise à jour, l'utilisation du même ID empêche qu'elle soit traitée comme une nouvelle session distincte.
  • Utilisez le traitement par lot pour les types de données et les points d'itinéraire : pour réduire la surcharge d'entrée/sortie et préserver l'autonomie de la batterie, regroupez vos points de données dans un seul appel insertRecords au lieu d'écrire chaque point individuellement.
  • Évitez d'écrire des données en double : utilisez des ID client : lorsque vous créez des enregistrements, définissez un metadata.clientRecordId. Santé Connect l'utilise pour identifier les enregistrements uniques. Si vous tentez d'écrire un enregistrement avec un clientRecordId qui existe déjà, Santé Connect ignore le doublon ou met à jour l'enregistrement existant au lieu d'en créer un. La définition d'un metadata.clientRecordId est le moyen le plus efficace d'éviter les doublons lors des nouvelles tentatives de synchronisation ou des réinstallations d'applications.
    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"
        )
    )
  • Vérifiez les données existantes : avant de synchroniser, interrogez la plage de temps pour voir si des enregistrements de votre application existent déjà.
  • Validez la précision du GPS : filtrez les échantillons GPS de faible précision (par exemple, les points avec un rayon de précision horizontale élevé) avant d'écrire dans le ExerciseRoute pour vérifier que la carte est propre et professionnelle.
  • Assurez-vous que les codes temporels ne se chevauchent pas : vérifiez qu'une nouvelle session ne démarre pas avant la fin de la précédente. Les sessions qui se chevauchent peuvent entraîner des conflits dans les tableaux de bord de remise en forme et les calculs récapitulatifs.
  • Fournissez des justifications claires pour l'autorisation : utilisez le flux Permission.createIntent pour expliquer pourquoi votre application a besoin d'accéder aux données de santé, par exemple : "Pour surveiller les tendances de votre tension artérielle et fournir des insights".
  • Gérez la mise en pause et la reprise : vérifiez que votre application gère correctement les pauses. Lorsqu'un utilisateur met en pause, arrêtez de collecter les points d'itinéraire et les types de données afin que le rythme moyen et la durée restent précis.
  • Testez les sessions de longue durée : surveillez la consommation de la batterie pendant les sessions de plusieurs heures pour vérifier que votre intervalle de traitement par lot et l'utilisation des capteurs ne vident pas l'appareil.
  • Alignez les codes temporels sur les taux des capteurs : faites correspondre les codes temporels de vos enregistrements à la fréquence réelle de vos capteurs pour maintenir une haute fidélité des données.

Tests

Pour vérifier l'exactitude des données et une expérience utilisateur de haute qualité, suivez ces stratégies de test et consultez la documentation officielle Tester les principaux cas d'utilisation.

Outils de validation

  • Boîte à outils Santé Connect: utilisez cette application associée pour inspecter manuellement les enregistrements, supprimer les données de test et simuler des modifications dans la base de données. C'est le meilleur moyen de vérifier que vos enregistrements sont stockés correctement.
  • Tests unitaires avec FakeHealthConnectClient : utilisez la bibliothèque de test pour vérifier comment votre application gère les cas extrêmes, comme la révocation d'autorisation ou les exceptions d'API sans avoir besoin d'un appareil physique.

Checklist pour la qualité

Architecture type

Une implémentation d'entraînement inclut généralement les éléments suivants :

Component Manages
Contrôleur de session État de la session
Minuteur
Logique de traitement par lot
Contrôleurs de types de données
Échantillonnage de la localisation
Couche de dépôt (encapsule les opérations Santé Connect) : Insérer une session
Insérer des types de données
Insérer des points d'itinéraire
Lire les récapitulatifs de session
Couche d'interface utilisateur (affiche) : Durée
Types de données en direct
Aperçu de la carte
Calculs fractionnés
Tracé GPS en direct

Dépannage

Problème constaté Cause possible Résolution
Itinéraire non associé à la session Incompatibilité de l'ID de session ou de la plage de temps. Vérifiez que l'ExerciseRoute est écrit avec une plage de temps qui s'inscrit entièrement dans la durée de l'ExerciseSessionRecord. Vérifiez que vous utilisez des ID cohérents si vous référencez la session ultérieurement. Consultez Enregistrer des itinéraires d'exercice.
Types de données manquants (par exemple, fréquence cardiaque) Autorisations d'écriture manquantes ou filtres temporels incorrects. Vérifiez que vous avez demandé l'autorisation pour le type de données spécifique et que l'utilisateur l'a accordée. Vérifiez que votre ReadRecordsRequest utilise un TimeRangeFilter qui correspond à la session. Consultez Autorisations.
Échec de l'écriture de la session Chevauchement des codes temporels. Santé Connect peut rejeter les enregistrements qui se chevauchent avec des données existantes de la même application. Vérifiez que la startTime d'une nouvelle session est postérieure à l'endTime de la précédente.
Aucune donnée GPS enregistrée Le service de premier plan a été arrêté ou est inactif. Pour collecter des données lorsque l'écran est éteint, vous devez utiliser un service de premier plan avec l'attribut foregroundServiceType="health" ou de localisation.
Des enregistrements en double s'affichent clientRecordId manquant. Attribuez un clientRecordId unique dans les Metadata de chaque enregistrement. Cela permet à Santé Connect d'effectuer une déduplication si les mêmes données sont écrites deux fois lors d'une nouvelle tentative de synchronisation. Consultez Bonnes pratiques.

Étapes de débogage courantes

Vérifiez l'état de l'autorisation. Appelez toujours getPermissionStatus() avant de tenter une opération de lecture ou d'écriture. Les utilisateurs peuvent révoquer les autorisations dans les paramètres système à tout moment.
Vérifiez le mode d'exécution. Si votre application ne collecte pas de données en arrière-plan, vérifiez que vous avez déclaré les autorisations correctes dans votre fichier AndroidManifest.xml et que l'utilisateur n'a pas placé l'application en mode "Batterie limitée".