使用 FHIR Engine 库管理 FHIR 资源

1. 准备工作

构建内容

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

学习内容

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

所需条件

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

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

HAPI FHIR 是一个广受欢迎的开源 FHIR 服务器。在本 Codelab 中,我们使用本地 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
    
    不过,这可能需要很长时间才能完成,对于此 Codelab 来说并不是必需的。
  4. 在浏览器中打开网址 http://localhost:8080/fhir/Patient/,验证服务器上是否有测试数据。您应该会看到包含 FHIR Bundle 中患者数据的页面的文本 HTTP 200 OKResponse Body 部分作为搜索结果,并带有 total 个计数。服务器上的测试数据

3. 设置 Android 应用

下载代码

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

此 Codelab 的起始项目位于 codelabs/engine 中。

将应用导入 Android Studio

首先,将 starter 应用导入 Android Studio。

打开 Android Studio,选择 Import Project (Gradle, Eclipse ADT, etc.),然后从之前下载的源代码中选择 codelabs/engine/ 文件夹。

Android Studio 启动屏幕

将项目与 Gradle 文件同步

为方便起见,我们已将 FHIR 引擎库依赖项添加到项目中。这样,您就可以在应用中集成 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 实例

如需将 FHIR Engine 集成到 Android 应用中,您需要使用 FHIR Engine 库并启动 FHIR Engine 实例。下列步骤将引导您完成此流程。

  1. 前往您的 Application 类(在本例中为 FhirApplication.kt),该类位于 app/src/main/java/com/google/android/fhir/codelabs/engine 中。
  2. onCreate() 方法内,添加以下代码以初始化 FHIR 引擎:
      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 专门用于本地主机,可通过 Android 模拟器访问。不妨了解详情
  3. FhirApplication 类中,添加以下代码行以延迟实例化 FHIR 引擎:
      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 服务器发起一次性同步。然后,它会根据同步进程的状态更新界面。
  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 的患者:同样,搜索地址城市为 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 引擎库中使用 Sync API、Data Access API 和 Search API

后续步骤

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

了解详情