使用 FHIR Engine 程式庫管理 FHIR 資源

1. 事前準備

建構項目

在本程式碼研究室中,您將使用 FHIR Engine 程式庫建構 Android 應用程式。應用程式會使用 FHIR Engine 程式庫,從 FHIR 伺服器下載 FHIR 資源,並將所有本機變更上傳至伺服器。

課程內容

  • 如何使用 Docker 建立本機 HAPI FHIR 伺服器
  • 如何將 FHIR Engine 程式庫整合至 Android 應用程式
  • 如何使用 Sync API 設定一次性或週期性工作,下載及上傳 FHIR 資源
  • 如何使用 Search API
  • 如何使用資料存取 API 在本機建立、讀取、更新及刪除 FHIR 資源

軟硬體需求

如果您從未建構 Android 應用程式,可以先建構第一個應用程式

2. 使用測試資料設定本機 HAPI FHIR 伺服器

HAPI FHIR 是熱門的開放原始碼 FHIR 伺服器,在程式碼研究室中,我們會使用本機 HAPI FHIR 伺服器,供 Android 應用程式連線。

設定本機 HAPI FHIR 伺服器

  1. 在終端機中執行下列指令,取得最新版 HAPI FHIR 映像檔
    docker pull hapiproject/hapi:latest
    
  2. 使用 Docker Desktop 執行先前下載的映像檔 hapiproject/hapi,或執行下列指令,建立 HAPI FHIR 容器
    docker run -p 8080:8080 hapiproject/hapi:latest
    
    瞭解詳情
  3. 在瀏覽器中開啟網址 http://localhost:8080/,檢查伺服器。您應該會看到 HAPI FHIR 網頁介面。HAPI FHIR 網頁介面

使用測試資料填入本機 HAPI FHIR 伺服器

如要測試應用程式,我們需要在伺服器上準備一些測試資料。我們將使用 Synthea 生成的合成資料。

  1. 首先,我們需要從 synthea-samples 下載範例資料。下載並解壓縮 synthea_sample_data_fhir_r4_sep2019.zip。解壓縮後的樣本資料包含許多 .json 檔案,每個檔案都是個別病患的交易組合。
  2. 我們會將三位病患的測試資料上傳至本機 HAPI FHIR 伺服器。在包含 JSON 檔案的目錄中執行下列指令
    curl -X POST -H "Content-Type: application/json" -d @./Aaron697_Brekke496_2fa15bc7-8866-461a-9000-f739e425860a.json http://localhost:8080/fhir/
    curl -X POST -H "Content-Type: application/json" -d @./Aaron697_Stiedemann542_41166989-975d-4d17-b9de-17f94cb3eec1.json http://localhost:8080/fhir/
    curl -X POST -H "Content-Type: application/json" -d @./Abby752_Kuvalis369_2b083021-e93f-4991-bf49-fd4f20060ef8.json http://localhost:8080/fhir/
    
  3. 如要將所有病患的測試資料上傳至伺服器,請執行
    for f in *.json; do curl -X POST -H "Content-Type: application/json" -d @$f http://localhost:8080/fhir/ ; done
    
    不過,這項作業可能需要很長時間才能完成,而且對於本程式碼研究室而言並非必要。
  4. 在瀏覽器中開啟網址 http://localhost:8080/fhir/Patient/,確認伺服器上是否有測試資料。您應該會看到「HTTP 200 OK」文字,以及網頁的「Response Body」部分,其中包含 FHIR 組合中的病患資料,並以搜尋結果的形式顯示「total」計數。測試伺服器上的資料

3. 設定 Android 應用程式

下載程式碼

如要下載這個程式碼研究室的程式碼,請複製 Android FHIR SDK 存放區:git clone https://github.com/google/android-fhir.git

本程式碼研究室的範例專案位於 codelabs/engine

將應用程式匯入 Android Studio

首先,請將範例應用程式匯入 Android Studio。

開啟 Android Studio,選取「Import Project (Gradle, Eclipse ADT, etc.)」,然後從先前下載的原始碼中選擇 codelabs/engine/ 資料夾。

Android Studio 啟動畫面

將專案與 Gradle 檔案同步

為方便起見,FHIR Engine 程式庫依附元件已新增至專案。這樣一來,您就能在應用程式中整合 FHIR Engine 程式庫。請觀察專案 app/build.gradle.kts 檔案結尾的下列程式碼:

dependencies {
    // ...

    implementation("com.google.android.fhir:engine:1.1.0")
}

為確保應用程式可使用所有依附元件,您應在此時將專案與 Gradle 檔案同步處理。

從 Android Studio 工具列選取「Sync Project with Gradle Files」 (Gradle 同步按鈕)。您也可以再次執行應用程式,檢查依附元件是否正常運作。

執行範例應用程式

專案匯入 Android Studio 後,即可首次執行應用程式。

啟動 Android Studio 模擬器,然後按一下 Android Studio 工具列中的「Run」圖示 (「Run」按鈕)。

Hello World 應用程式

4. 建立 FHIR Engine 執行個體

如要在 Android 應用程式中加入 FHIR Engine,您需要使用 FHIR Engine 程式庫,並啟動 FHIR Engine 的執行個體。請按照下列步驟完成程序。

  1. 前往您的 Application 類別,在本範例中為 FhirApplication.kt,位於 app/src/main/java/com/google/android/fhir/codelabs/engine 中。
  2. onCreate() 方法中新增下列程式碼,初始化 FHIR Engine:
      FhirEngineProvider.init(
          FhirEngineConfiguration(
            enableEncryptionIfSupported = true,
            RECREATE_AT_OPEN,
            ServerConfiguration(
              baseUrl = "http://10.0.2.2:8080/fhir/",
              httpLogger =
                HttpLogger(
                  HttpLogger.Configuration(
                    if (BuildConfig.DEBUG) HttpLogger.Level.BODY else HttpLogger.Level.BASIC,
                  ),
                ) {
                  Log.d("App-HttpLog", it)
                },
            ),
          ),
      )
    
    注意:
    • enableEncryptionIfSupported:如果裝置支援資料加密功能,則啟用這項功能。
    • RECREATE_AT_OPEN:決定資料庫錯誤策略。如果開啟時發生錯誤,系統會重新建立資料庫。
    • baseUrl in ServerConfiguration:這是 FHIR 伺服器的基準網址。提供的 IP 位址 10.0.2.2 是專為 localhost 保留,可從 Android 模擬器存取。瞭解詳情
  3. FhirApplication 類別中,新增下列程式碼行,以延遲例項化 FHIR Engine:
      private val fhirEngine: FhirEngine by
          lazy { FhirEngineProvider.getInstance(this) }
    
    這可確保 FhirEngine 執行個體只會在首次存取時建立,不會在應用程式啟動時立即建立。
  4. FhirApplication 類別中新增下列便利方法,方便在整個應用程式中存取:
    companion object {
        fun fhirEngine(context: Context) =
            (context.applicationContext as FhirApplication).fhirEngine
    }
    
    這個靜態方法可讓您使用內容,從應用程式的任何位置擷取 FHIR Engine 執行個體。

5. 與 FHIR 伺服器同步處理資料

  1. 建立新的 DownloadWorkManagerImpl.kt 類別。在本課程中,您將定義應用程式如何從清單中擷取下一個要下載的資源:
      class DownloadWorkManagerImpl : DownloadWorkManager {
        private val urls = LinkedList(listOf("Patient"))
    
        override suspend fun getNextRequest(): DownloadRequest? {
          val url = urls.poll() ?: return null
          return DownloadRequest.of(url)
        }
    
        override suspend fun getSummaryRequestUrls() = mapOf<ResourceType, String>()
    
        override suspend fun processResponse(response: Resource): Collection<Resource> {
          var bundleCollection: Collection<Resource> = mutableListOf()
          if (response is Bundle && response.type == Bundle.BundleType.SEARCHSET) {
            bundleCollection = response.entry.map { it.resource }
          }
          return bundleCollection
        }
      }
    
    這個類別有要下載的資源類型佇列。這個類別會處理回應,並從傳回的套件中擷取資源,然後儲存到本機資料庫。
  2. 建立新類別 AppFhirSyncWorker.kt 這個類別會定義應用程式如何使用背景工作者,與遠端 FHIR 伺服器同步。
    class AppFhirSyncWorker(appContext: Context, workerParams: WorkerParameters) :
      FhirSyncWorker(appContext, workerParams) {
    
      override fun getDownloadWorkManager() = DownloadWorkManagerImpl()
    
      override fun getConflictResolver() = AcceptLocalConflictResolver
    
      override fun getFhirEngine() = FhirApplication.fhirEngine(applicationContext)
    
      override fun getUploadStrategy() =
        UploadStrategy.forBundleRequest(
          methodForCreate = HttpCreateMethod.PUT,
          methodForUpdate = HttpUpdateMethod.PATCH,
          squash = true,
          bundleSize = 500,
        )
    }
    
    我們已定義要用於同步處理的下載管理工具、衝突解決工具和 FHIR 引擎執行個體。
  3. 在 ViewModel PatientListViewModel.kt 中,您將設定一次性同步機制。找出並將這段程式碼新增至 triggerOneTimeSync() 函式:
    viewModelScope.launch {
          Sync.oneTimeSync<AppFhirSyncWorker>(getApplication())
            .shareIn(this, SharingStarted.Eagerly, 10)
            .collect { _pollState.emit(it) }
        }
    
    這個協同程式會使用我們稍早定義的 AppFhirSyncWorker,與 FHIR 伺服器啟動一次性同步作業。然後根據同步處理程序的狀態更新 UI。
  4. PatientListFragment.kt 檔案中,更新 handleSyncJobStatus 函式的主體:
    when (syncJobStatus) {
        is SyncJobStatus.Finished -> {
            Toast.makeText(requireContext(), "Sync Finished", Toast.LENGTH_SHORT).show()
            viewModel.searchPatientsByName("")
        }
        else -> {}
    }
    
    在這裡,同步程序完成後,系統會顯示通知使用者的訊息,然後應用程式會叫用空白名稱的搜尋,顯示所有病患。

現在一切都設定完畢,請執行應用程式。按一下選單中的 Sync 按鈕。如果一切正常,您應該會看到應用程式下載並顯示本機 FHIR 伺服器中的病患。

病患名單

6. 修改及上傳病患資料

在本節中,我們將逐步說明如何根據特定條件修改病患資料,並將更新後的資料上傳至 FHIR 伺服器。具體來說,我們會為居住在 WakefieldTaunton 的病患更換地址城市。

步驟 1:在 PatientListViewModel 中設定修改邏輯

本節中的程式碼會新增至 PatientListViewModeltriggerUpdate 函式中

  1. 存取 FHIR 引擎:首先,請在 PatientListViewModel.kt 中取得 FHIR 引擎的參照。
    viewModelScope.launch {
       val fhirEngine = FhirApplication.fhirEngine(getApplication())
    
    這段程式碼會在 ViewModel 的範圍內啟動協同程式,並初始化 FHIR 引擎。
  2. 搜尋來自韋克菲爾德的病患:使用 FHIR 引擎搜尋地址城市為 Wakefield 的病患。
    val patientsFromWakefield =
         fhirEngine.search<Patient> {
           filter(
             Patient.ADDRESS_CITY,
             {
               modifier =  StringFilterModifier.MATCHES_EXACTLY
               value = "Wakefield"
             }
           )
         }
    
    在這裡,我們使用 FHIR 引擎的 search 方法,根據病患的地址城市進行篩選。結果會列出 Wakefield 的病患。
  3. 搜尋來自 Taunton 的病患:同樣地,搜尋地址城市為 Taunton 的病患。
    val patientsFromTaunton =
         fhirEngine.search<Patient> {
           filter(
             Patient.ADDRESS_CITY,
             {
               modifier =  StringFilterModifier.MATCHES_EXACTLY
               value = "Taunton"
             }
           )
         }
    
    我們現在有兩份病患名單,分別來自韋克菲爾德和湯頓。
  4. 修改病患地址:逐一檢查 patientsFromWakefield 清單中的病患,將他們的城市變更為 Taunton,然後在 FHIR 引擎中更新。
    patientsFromWakefield.forEach {
         it.resource.address.first().city = "Taunton"
         fhirEngine.update(it.resource)
    }
    
    同樣地,請更新 patientsFromTaunton 清單中的每位病患,將城市變更為 Wakefield
    patientsFromTaunton.forEach {
         it.resource.address.first().city = "Wakefield"
         fhirEngine.update(it.resource)
    }
    
  5. 啟動同步:在本機修改資料後,請觸發一次性同步,確保 FHIR 伺服器上的資料已更新。
    triggerOneTimeSync()
    }
    
    右大括號 } 表示開頭啟動的協同程式已結束。

步驟 2:測試功能

  1. 使用者介面測試:執行應用程式。按一下選單中的 Update 按鈕。您應該會看到病患 Aaron697Abby752 的地址城市已互換。
  2. 伺服器驗證:開啟瀏覽器並前往 http://localhost:8080/fhir/Patient/。確認病患 Aaron697Abby752 的地址城市已在本機 FHIR 伺服器上更新。

按照上述步驟操作後,您已成功導入機制來修改病患資料,並將變更同步至 FHIR 伺服器。

7. 依姓名搜尋病患

依姓名搜尋病患資訊,是簡單易用的資訊擷取方式。我們會逐步說明如何在應用程式中導入這項功能。

步驟 1:更新函式簽章

前往 PatientListViewModel.kt 檔案,找出名為 searchPatientsByName 的函式。我們將在這個函式中加入程式碼。

如要根據提供的名稱查詢篩選結果,並發出結果以供 UI 更新,請加入下列條件式程式碼區塊:

    viewModelScope.launch {
      val fhirEngine = FhirApplication.fhirEngine(getApplication())
      if (nameQuery.isNotEmpty()) {
        val searchResult = fhirEngine.search<Patient> {
          filter(
            Patient.NAME,
            {
              modifier = StringFilterModifier.CONTAINS
              value = nameQuery
            },
          )
        }
        liveSearchedPatients.value  =  searchResult.map { it.resource }
      }
    }

在這裡,如果 nameQuery 不為空白,搜尋功能會篩選結果,只納入名稱包含指定查詢的病患。

步驟 2:測試新的搜尋功能

  1. 重新啟動應用程式:完成這些變更後,請重建並執行應用程式。
  2. 搜尋病患:在病患清單畫面中,使用搜尋功能。現在您應該可以輸入名稱 (或部分名稱),據此篩選病患清單。

完成這些步驟後,您已強化應用程式,讓使用者能依姓名有效搜尋病患。這能大幅提升使用者體驗,以及資料擷取的效率。

8. 恭喜!

您已使用 FHIR Engine 程式庫管理應用程式中的 FHIR 資源:

  • 使用 Sync API 將 FHIR 資源與 FHIR 伺服器同步
  • 使用 Data Access API 建立、讀取、更新及刪除本機 FHIR 資源
  • 使用 Search API 搜尋本機 FHIR 資源

涵蓋內容

  • 如何設定本機 HAPI FHIR 伺服器
  • 如何將測試資料上傳至本機 HAPI FHIR 伺服器
  • 如何使用 FHIR Engine 程式庫建構 Android 應用程式
  • 如何在 FHIR Engine 程式庫中使用 Sync API、Data Access API 和 Search API

後續步驟

  • 探索 FHIR Engine 程式庫的說明文件
  • 探索 Search API 的進階功能
  • 在自己的 Android 應用程式中套用 FHIR Engine 程式庫

瞭解詳情