Mulai menggunakan PHR di API framework Android

Untuk menggunakan API framework Android untuk Personal Health Records (PHR), Anda harus:

  1. Mengonfigurasi lingkungan menggunakan Android Studio
  2. Membuat instance HealthConnectManager
  3. Menangani izin
  4. Membuat sumber data

Mengonfigurasi lingkungan menggunakan Android Studio

Untuk memiliki akses ke API terbaru, Anda juga memerlukan akses ke alat terbaru yang relevan:

  • Instal Android Studio versi terbaru.
  • Di Android Studio, buka Tools > SDK Manager.
    • Di tab SDK Platforms, pilih Android Baklava.
    • Versi ini mencakup API level 36 dan PHR API.
    • Di tab SDK Tools, pilih Android SDK Build-Tools terbaru yang tersedia.
    • Klik OK untuk menginstal SDK.
  • Gunakan versi terbaru plugin Android Gradle yang tersedia.

Mendeklarasikan level API

Untuk mengakses PHR API, Anda harus menargetkan versi Android yang sesuai. Hal ini dilakukan dengan mendeklarasikan API level di app/build.gradle.

...
compileSdk = 36

defaultConfig {
  targetSdk = 36
...

Pelajari lebih lanjut cara mengonfigurasi API level dalam dokumentasi elemen uses-sdk dan Menyiapkan SDK Android 16.

Mendeklarasikan Izin

Tambahkan izin PHR baru di AndroidManifest.xml. Deklarasikan izin hanya untuk jenis data yang ingin Anda gunakan dalam aplikasi. Daftar lengkap izin terkait medis ditampilkan di blok kode. Izin non-terkait PHR untuk Health Connect tidak disertakan.

...
  <!--  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"/>
...

Jika aplikasi mencoba menggunakan API yang memerlukan izin, dan izin tersebut tidak dideklarasikan dalam manifes aplikasi, SecurityException akan ditampilkan.

Untuk pengecualian jenis SecurityException, pesan error yang berguna akan tersedia di exception.localizedMessage.

Membuat instance HealthConnectManager

HealthConnectManager adalah class yang bertanggung jawab menangani interaksi izin serta membaca dan menulis ke repositori data lokal Health Connect. Kita akan mempelajari metode instance HealthConnectManager di bagian berikutnya. Karena HealthConnectManager mengekspos layanan sistem, Anda tidak dapat membuat instance class ini secara langsung dan harus menggunakan getSystemService. Secara khusus, perhatikan bahwa layanan sistem digabungkan dengan erat dengan konteks yang dibuat instance-nya dan tidak boleh dibuat dapat diakses di luar konteks tersebut.

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

Menangani izin

Pengguna aplikasi Anda harus memberikan izin kepada aplikasi Anda untuk mengakses data Health Connect. Untuk melakukannya, luncurkan aktivitas baru dengan izin yang ditentukan, dan gunakan daftar izin yang diberikan. UI aktivitas baru akan memungkinkan pengguna memilih izin yang diperlukan untuk diberikan ke aplikasi Anda. Minta izin hanya untuk jenis data yang ingin digunakan aplikasi Anda.

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)

Mencoba membaca atau menulis tanpa izin yang diperlukan akan menghasilkan HealthConnectException.

Untuk pengecualian jenis HealthConnectException, pesan error yang berguna akan tersedia di exception.localizedMessage.

Membuat sumber data

Untuk menulis data kesehatan ke Health Connect, aplikasi Anda harus membuat sumber data terlebih dahulu untuk menyimpan informasi. Sumber data biasanya mewakili API atau sistem medis tertentu.

Dalam contoh, kita membuat sumber data bernama My Hospital dan menentukan versi 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"),
)

Menulis data

Siapkan contoh data FHIR dalam JSON. Ada berbagai sumber di seluruh web dengan contoh data dalam 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"
      }
    }
  ]
}

Sisipkan data:

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 menggunakan daftar UpsertMedicalResourceRequest sebagai argumen. Jika setiap UpsertMedicalResourceRequest individual yang diteruskan dalam panggilan gagal disisipkan, tidak ada operasi tulis yang akan di-commit ke repositori untuk seluruh daftar UpsertMedicalResourceRequest.

Jika permintaan berisi ID MedicalDataSource yang tidak valid, API akan menampilkan IllegalArgumentException. Jika permintaan dianggap tidak valid karena alasan lain, pemanggil akan menerima HealthConnectException.

Kunci unik untuk permintaan tertentu adalah kombinasi ID sumber data, jenis resource FHIR, dan ID resource FHIR. Jika ketiga item dalam permintaan ini cocok dengan data yang ada, pembaruan akan dipicu. Jika tidak, data baru akan dibuat.

Membaca data

Baca data menurut jenis, lalu tangani hasilnya sesuai keinginan Anda.

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
}