Premiers pas avec la PHR sur les API du framework Android

Pour utiliser les API du framework Android pour les dossiers médicaux personnels (PHR), vous devez:

  1. Configurer l'environnement à l'aide d'Android Studio
  2. Instancier HealthConnectManager
  3. Gérer les autorisations
  4. Créer des sources de données

Configurer l'environnement à l'aide d'Android Studio

Pour accéder aux dernières API, vous devez également avoir accès aux derniers outils pertinents:

  • Installez la dernière version d'Android Studio.
  • Dans Android Studio, accédez à Outils > SDK Manager.
    • Dans l'onglet "SDK Platforms" (Plates-formes SDK), sélectionnez Android Baklava.
    • Cette version inclut le niveau d'API 36 et les API PHR.
    • Dans l'onglet "SDK Tools" (Outils SDK), sélectionnez la dernière version disponible de Android SDK Build-Tools.
    • Cliquez sur OK pour installer le SDK.
  • Utilisez la dernière version disponible du plug-in Android Gradle.

Déclarer des niveaux d'API

Pour accéder aux API PHR, vous devez cibler la version appropriée d'Android. Pour ce faire, déclarez les niveaux d'API dans app/build.gradle.

...
compileSdk = 36

defaultConfig {
  targetSdk = 36
...

Pour en savoir plus sur la configuration des niveaux d'API, consultez les pages Élément uses-sdk et Configurer le SDK Android 16.

Déclarer des autorisations

Ajoutez des autorisations de dossier médical personnel dans AndroidManifest.xml. Ne déclarez des autorisations que pour les types de données que vous prévoyez d'utiliser dans votre application. La liste complète des autorisations liées aux soins médicaux s'affiche dans le bloc de code. Les autorisations non liées au dossier médical électronique pour Santé Connect ne sont pas incluses.

...
  <!--  Medical permissions -->
  <uses-permission android:name="android.permission.health.WRITE_MEDICAL_DATA"/>
  <uses-permission android:name="android.permission.health.READ_MEDICAL_DATA_ALLERGIES_INTOLERANCES"/>
  <uses-permission android:name="android.permission.health.READ_MEDICAL_DATA_CONDITIONS"/>
  <uses-permission android:name="android.permission.health.READ_MEDICAL_DATA_IMMUNIZATIONS"/>
  <uses-permission android:name="android.permission.health.READ_MEDICAL_DATA_LABORATORY_RESULTS"/>
  <uses-permission android:name="android.permission.health.READ_MEDICAL_DATA_MEDICATIONS"/>
  <uses-permission android:name="android.permission.health.READ_MEDICAL_DATA_PERSONAL_DETAILS"/>
  <uses-permission android:name="android.permission.health.READ_MEDICAL_DATA_PRACTITIONER_DETAILS"/>
  <uses-permission android:name="android.permission.health.READ_MEDICAL_DATA_PREGNANCY"/>
  <uses-permission android:name="android.permission.health.READ_MEDICAL_DATA_PROCEDURES"/>
  <uses-permission android:name="android.permission.health.READ_MEDICAL_DATA_SOCIAL_HISTORY"/>
  <uses-permission android:name="android.permission.health.READ_MEDICAL_DATA_VISITS"/>
  <uses-permission android:name="android.permission.health.READ_MEDICAL_DATA_VITAL_SIGNS"/>
...

Si une application tente d'utiliser une API qui nécessite une autorisation et que cette autorisation n'est pas déclarée dans le fichier manifeste de l'application, une exception SecurityException est générée.

Pour une exception de type SecurityException, un message d'erreur utile sera disponible dans exception.localizedMessage.

Instancier HealthConnectManager

HealthConnectManager est la classe chargée de gérer les interactions d'autorisation, ainsi que les lectures et les écritures dans le dépôt de données local de Santé Connect. Nous explorerons les méthodes d'instance de HealthConnectManager dans les sections suivantes. Comme HealthConnectManager expose un service système, vous ne pouvez pas instancier cette classe directement et devez utiliser getSystemService. Notez en particulier que le service système est étroitement associé au contexte dans lequel il est instancié et ne doit pas être accessible en dehors de ce contexte.

import android.health.connect.HealthConnectManager
...
val healthConnectManager: HealthConnectManager = requireNotNull(applicationContext.getSystemService(HealthConnectManager::class.java))

Gérer les autorisations

L'utilisateur de votre application doit lui accorder l'autorisation d'accéder aux données Santé Connect. Pour ce faire, lancez une nouvelle activité avec les autorisations spécifiées, puis utilisez la liste des autorisations accordées qui en résulte. L'UI de la nouvelle activité permettra à l'utilisateur de sélectionner les autorisations qu'il doit accorder à votre application. Ne demandez des autorisations que pour les types de données que vous prévoyez d'utiliser.

val MEDICAL_PERMISSIONS = arrayOf(
            "android.permission.health.READ_MEDICAL_DATA_ALLERGIES_INTOLERANCES",
            "android.permission.health.READ_MEDICAL_DATA_CONDITIONS",
            "android.permission.health.READ_MEDICAL_DATA_IMMUNIZATIONS",
            "android.permission.health.READ_MEDICAL_DATA_LABORATORY_RESULTS",
            "android.permission.health.READ_MEDICAL_DATA_MEDICATIONS",
            "android.permission.health.READ_MEDICAL_DATA_PERSONAL_DETAILS",
            "android.permission.health.READ_MEDICAL_DATA_PRACTITIONER_DETAILS",
            "android.permission.health.READ_MEDICAL_DATA_PREGNANCY",
            "android.permission.health.READ_MEDICAL_DATA_PROCEDURES",
            "android.permission.health.READ_MEDICAL_DATA_SOCIAL_HISTORY",
            "android.permission.health.READ_MEDICAL_DATA_VISITS",
            "android.permission.health.READ_MEDICAL_DATA_VITAL_SIGNS",
            "android.permission.health.WRITE_MEDICAL_DATA",
        )
...
private lateinit var mRequestPermissionLauncher: ActivityResultLauncher<Array<String>>
...
mRequestPermissionLauncher =
        registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
            permissionMap: Map<String, Boolean> ->
            requestPermissionResultHandler(permissionMap)
        }
}
...
private fun requestPermissionResultHandler(permissionMap: Map<String, Boolean>) {
    // Evaluate permissionMap and handle any missing permissions
}
...
mRequestPermissionLauncher.launch(MEDICAL_PERMISSIONS)

Si vous tentez de lire ou d'écrire sans autorisation requise, une HealthConnectException s'affiche.

Pour une exception de type HealthConnectException, un message d'erreur utile sera disponible dans exception.localizedMessage.

Créer des sources de données

Pour écrire des données de santé dans Santé Connect, votre application doit d'abord créer une source de données pour stocker les informations. Une source de données représente généralement une API ou un système médical spécifique.

Dans l'exemple, nous créons une source de données appelée My Hospital et spécifions la version FHIR.

import android.health.connect.CreateMedicalDataSourceRequest
import android.health.connect.HealthConnectManager
import android.health.connect.datatypes.FhirVersion
import android.health.connect.datatypes.MedicalDataSource
import android.net.Uri
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.os.asOutcomeReceiver
import kotlinx.coroutines.suspendCancellableCoroutine
...
private suspend fun createMedicalDataSource(
    fhirBaseUri: Uri,
    displayName: String,
    fhirVersion: FhirVersion,
): String {
    val dataSource =
        suspendCancellableCoroutine<MedicalDataSource> { continuation ->
            healthConnectManager.createMedicalDataSource(
                CreateMedicalDataSourceRequest.Builder(fhirBaseUri,
                                                       displayName,
                                                       fhirVersion).build(),
                Runnable::run,
                continuation.asOutcomeReceiver(),
            )
        }
    Log.d("CREATE_DATA_SOURCE", "Created source: $dataSource")
    return "Created data source: $displayName"
}
...
createMedicalDataSource(
    Uri.parse("example.fhir.com/R4/123"),
    "My Hospital",
    FhirVersion.parseFhirVersion("4.0.1"),
)

Écrire des enregistrements

Préparez des exemples d'enregistrements FHIR au format JSON. Il existe plusieurs sources sur le Web proposant des exemples de données au format FHIR.

{
  "resourceType": "Immunization",
  "id": "immunization-1",
  "status": "completed",
  "vaccineCode": {
    "coding": [
      {
        "system": "http://hl7.org/fhir/sid/cvx",
        "code": "115"
      },
      {
        "system": "http://hl7.org/fhir/sid/ndc",
        "code": "58160-842-11"
      }
    ],
    "text": "Tdap"
  },
  "patient": {
    "reference": "Patient/patient_1",
    "display": "Example, Anne"
  },
  "encounter": {
    "reference": "Encounter/encounter_unk",
    "display": "GP Visit"
  },
  "occurrenceDateTime": "2018-05-21",
  "primarySource": true,
  "manufacturer": {
    "display": "Sanofi Pasteur"
  },
  "lotNumber": "1",
  "site": {
    "coding": [
      {
        "system": "http://terminology.hl7.org/CodeSystem/v3-ActSite",
        "code": "LA",
        "display": "Left Arm"
      }
    ],
    "text": "Left Arm"
  },
  "route": {
    "coding": [
      {
        "system": "http://terminology.hl7.org/CodeSystem/v3-RouteOfAdministration",
        "code": "IM",
        "display": "Injection, intramuscular"
      }
    ],
    "text": "Injection, intramuscular"
  },
  "doseQuantity": {
    "value": 0.5,
    "unit": "mL"
  },
  "performer": [
    {
      "function": {
        "coding": [
          {
            "system": "http://terminology.hl7.org/CodeSystem/v2-0443",
            "code": "AP",
            "display": "Administering Provider"
          }
        ],
        "text": "Administering Provider"
      },
      "actor": {
        "reference": "Practitioner/practitioner_1",
        "type": "Practitioner",
        "display": "Dr Maria Hernandez"
      }
    }
  ]
}

Insérez les données:

import android.health.connect.UpsertMedicalResourceRequest
import android.health.connect.datatypes.MedicalResource
...
private suspend fun loadJSONFromAsset(assetName: String): String {
...
private suspend fun upsertMedicalResources(
    requests: List<UpsertMedicalResourceRequest>
): List<MedicalResource> {
    Log.d("UPSERT_RESOURCES", "Writing ${requests.size} resources")
    val resources =
        suspendCancellableCoroutine<List<MedicalResource>> { continuation ->
            healthConnectManager.upsertMedicalResources(
                requests,
                Runnable::run,
                continuation.asOutcomeReceiver(),
            )
        }
    Log.d("UPSERT_RESOURCES", "Wrote ${resources.size} resources")
    return resources
}
...
private suspend fun insertResource(insertedDataSourceId: String, resource: String): String {
    val insertedResources =
        upsertMedicalResources(
            listOf(
                UpsertMedicalResourceRequest.Builder(
                        insertedDataSourceId,
                        FhirVersion.parseFhirVersion("4.0.1"),
                        resource,
                    )
                    .build()
            )
        )
    return insertedResources.joinToString(
        separator = "\n",
        transform = MedicalResource::toString,
    )
}
...
val immunizationResource =
    loadJSONFromAsset("immunization_1.json")
insertResource(dataSource.id, immunizationResource)

upsertMedicalResources utilise une liste de UpsertMedicalResourceRequest comme argument. Si l'insertion d'un UpsertMedicalResourceRequest individuel transmis dans l'appel échoue, aucune écriture ne sera effectuée dans le dépôt pour l'ensemble de la liste des UpsertMedicalResourceRequest.

Si une requête contient des ID MedicalDataSource non valides, l'API génère une exception IllegalArgumentException. Si une requête est jugée non valide pour toute autre raison, l'appelant reçoit un HealthConnectException.

La clé unique d'une requête particulière correspond à la combinaison de l'ID de la source de données, du type de ressource FHIR et de l'ID de la ressource FHIR. Si ces trois éléments d'une requête correspondent à un enregistrement existant, une mise à jour est déclenchée. Sinon, un nouvel enregistrement est créé.

Lire les enregistrements

Lisez les enregistrements par type, puis gérez les résultats comme vous le souhaitez.

import android.health.connect.ReadMedicalResourcesInitialRequest
import android.health.connect.ReadMedicalResourcesResponse
import android.health.connect.datatypes.MedicalResource.MEDICAL_RESOURCE_TYPE_IMMUNIZATIONS
...
private suspend fun readImmunization(): List<MedicalResource> {

    var receiver: OutcomeReceiver<ReadMedicalResourcesResponse, HealthConnectException>
    val request: ReadMedicalResourcesInitialRequest =
        ReadMedicalResourcesInitialRequest.Builder(MEDICAL_RESOURCE_TYPE_IMMUNIZATIONS).build()
    val resources =
        suspendCancellableCoroutine<ReadMedicalResourcesResponse> { continuation ->
                receiver = continuation.asOutcomeReceiver()
                healthConnectManager.readMedicalResources(request, Runnable::run, receiver)
            }
            .medicalResources
    Log.d("READ_MEDICAL_RESOURCES", "Read ${resources.size} resources")
    return resources
}