使用 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,或是執行下列指令
    docker run -p 8080:8080 hapiproject/hapi:latest
    
    ,建立 HAPI FHIR 容器 瞭解詳情
  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/,驗證伺服器上是否有測試資料。頁面中應會顯示含有 FHIR 套件的病患資料 HTTP 200 OKResponse Body 部分,因此搜尋結果會顯示 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:0.1.0-beta05")
}

為確保應用程式能夠使用所有依附元件,請在此時將專案與 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. 前往位於 app/src/main/java/com/google/android/fhir/codelabs/engine 的應用程式類別,在本例中為 FhirApplication.kt
  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:決定資料庫錯誤策略。在此情況下,如果開啟時發生錯誤,它會重新建立資料庫。
    • ServerConfiguration 中的 baseUrl:這是 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)
    }
    
    我們在這裡定義了要用哪些下載管理員、衝突解析器和 FHIR 引擎執行個體執行同步處理。
  3. 在 ViewModel (PatientListViewModel.kt) 中,您將設定一次性同步機制。請找出以下程式碼,並新增至 triggerOneTimeSync() 函式中:
    viewModelScope.launch {
          Sync.oneTimeSync<AppFhirSyncWorker>(getApplication())
            .shareIn(this, SharingStarted.Eagerly, 10)
            .collect { _pollState.emit(it) }
        }
    
    這個協同程式會使用先前定義的 AppFhirSyncWorker,啟動與 FHIR 伺服器之間的一次性同步處理。接著,瀏覽器會依據同步處理程序狀態更新使用者介面。
  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 中設定修改邏輯

本節中的程式碼已新增至 PatientListViewModel 中的 triggerUpdate 函式

  1. 存取 FHIR Engine:首先,請取得 PatientListViewModel.kt 中 FHIR 引擎的參考資料。
    viewModelScope.launch {
       val fhirEngine = FhirApplication.fhirEngine(getApplication())
    
    這段程式碼會在 ViewModel 範圍內啟動協同程式,並初始化 FHIR 引擎。
  2. 從 Wakefield 搜尋病患:使用 FHIR 引擎搜尋地址城市為 Wakefield 的病患。
    val patientsFromWakefield =
         fhirEngine.search<Patient> {
           filter(
             Patient.ADDRESS_CITY,
             {
               modifier =  StringFilterModifier.MATCHES_EXACTLY
               value = "Wakefield"
             }
           )
         }
    
    這裡,我們使用 FHIR 引擎的 search 方法,根據地址城市篩選病患。搜尋結果將顯示為威克菲爾德的病患清單。
  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. UI 測試:執行應用程式。按一下選單中的 Update 按鈕。您應該會看到病患 Aaron697Abby752 的城市地址已交換。
  2. 伺服器驗證:開啟瀏覽器並前往 http://localhost:8080/fhir/Patient/。確認本機 FHIR 伺服器上的病患 Aaron697Abby752 地址城市已更新。

完成下列步驟後,即代表您已成功實作修改病患資料,並將變更與 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 程式庫

瞭解詳情