使用 FHIR Engine 库管理 FHIR 资源

1. 准备工作

构建内容

在此 Codelab 中,您将使用 FHIR Engine 库构建一个 Android 应用。您的应用将使用 FHIR Engine 库从 FHIR 服务器下载 FHIR 资源,并将所有本地更改上传到服务器。

学习内容

  • 如何使用 Docker 创建本地 HAPI FHIR 服务器
  • 如何将 FHIR Engine 库集成到 Android 应用中
  • 如何使用同步 API 设置一次性或定期作业来下载和上传 FHIR 资源
  • 如何使用搜索 API
  • 如何使用数据访问 API 在本地创建、读取、更新和删除 FHIR 资源

所需条件

如果您之前没有构建过 Android 应用,可以先构建首个应用

2. 使用测试数据设置本地 HAPI FHIR 服务器

HAPI FHIR 是一种备受欢迎的开源 FHIR 服务器。在 Codelab 中,我们使用本地 HAPI FHIR 服务器供 Android 应用连接。

设置本地 HAPI FHIR 服务器

  1. 在终端中运行以下命令,获取 HAPI FHIR 的最新映像
    docker pull hapiproject/hapi:latest
    
  2. 通过以下任一方式创建 HAPI FHIR 容器:使用 Docker Desktop 运行之前下载的映像 hapiproject/hapi,或运行以下命令
    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
    
    不过,此操作可能需要很长时间才能完成,并且对于本 Codelab 而言并非必需。
  4. 在浏览器中打开网址 http://localhost:8080/fhir/Patient/,验证服务器上是否有测试数据。您应该会看到文本 HTTP 200 OK 和包含 FHIR Bundle 中患者数据的网页的 Response Body 部分作为搜索结果,其中包含 total 个数。服务器上的测试数据

3. 设置 Android 应用

下载代码

如需下载此 Codelab 的代码,请克隆 Android FHIR SDK 代码库:git clone https://github.com/google/android-fhir.git

此 Codelab 的起始项目位于 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”按钮)。

Hello World 应用

4. 创建 FHIR Engine 实例

如需将 FHIR Engine 纳入您的 Android 应用,您需要使用 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:确定数据库错误策略。在这种情况下,如果打开时发生错误,它会重新创建数据库。
    • 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 此类定义了应用将如何使用后台 worker 与远程 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 服务器的一次性同步。然后,它会根据同步进程的状态更新界面。
  4. PatientListFragment.kt 文件中,更新 handleSyncJobStatus 函数的正文:
    when (syncJobStatus) {
        is SyncJobStatus.Finished -> {
            Toast.makeText(requireContext(), "Sync Finished", Toast.LENGTH_SHORT).show()
            viewModel.searchPatientsByName("")
        }
        else -> {}
    }
    
    在此示例中,当同步过程完成时,系统会显示一条 Toast 消息来通知用户,然后应用会通过调用空名称的搜索来显示所有患者。

现在,一切都已设置完毕,请运行您的应用。点击菜单中的 Sync 按钮。如果一切正常,您应该会看到本地 FHIR 服务器中的患者数据被下载并显示在应用中。

患者列表

6. 修改和上传患者数据

在本部分中,我们将引导您完成以下流程:根据特定条件修改患者数据,并将更新后的数据上传到 FHIR 服务器。具体来说,我们将交换居住在 WakefieldTaunton 的患者的地址城市。

第 1 步:在 PatientListViewModel 中设置修改逻辑

此部分中的代码已添加到 PatientListViewModel 中的 triggerUpdate 函数中

  1. 访问 FHIR 引擎:首先,在 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 方法根据患者的地址城市过滤患者。结果将是来自 Wakefield 的患者列表。
  3. 搜索来自汤顿的患者:同样,搜索地址城市为 Taunton 的患者。
    val patientsFromTaunton =
         fhirEngine.search<Patient> {
           filter(
             Patient.ADDRESS_CITY,
             {
               modifier =  StringFilterModifier.MATCHES_EXACTLY
               value = "Taunton"
             }
           )
         }
    
    现在,我们有两份患者名单,一份来自 Wakefield,另一份来自 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/。验证本地 FHIR 服务器上患者 Aaron697Abby752 的地址城市是否已更新。

按照上述步骤操作后,您已成功实现一种机制,用于修改患者数据并将更改同步到 FHIR 服务器。

7. 按姓名搜索患者

按患者姓名搜索可以提供一种用户友好的信息检索方式。下面,我们将逐步介绍如何在应用中实现此功能。

第 1 步:更新函数签名

前往 PatientListViewModel.kt 文件,找到名为 searchPatientsByName 的函数。我们将在此函数中添加代码。

如需根据提供的名称查询过滤结果,并发出结果以供界面更新,请添加以下条件代码块:

    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 库中使用同步 API、数据访问 API 和搜索 API

后续步骤

  • 探索 FHIR Engine 库的文档
  • 探索 Search API 的高级功能
  • 在您自己的 Android 应用中应用 FHIR Engine 库

了解详情