使用 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. 创建 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/,验证测试数据在服务器上是否可用。您应该会看到页面中包含 FHIR 包中患者数据的 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

首先,将起始应用导入 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 实例

如需将 FHIR Engine 合并到您的 Android 应用中,您需要使用 FHIR Engine 库并启动 FHIR Engine 的实例。下面列出的步骤将引导您完成整个过程。

  1. 前往位于 app/src/main/java/com/google/android/fhir/codelabs/engine 中的 Application 类,在此示例中为 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
        }
      }
    
    此类具有它要下载的资源类型队列。它会处理响应并从返回的 bundle 中提取资源,这些资源会保存到本地数据库中。
  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 引擎:首先在 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/。验证患者 Aaron697Abby752 的地址城市是否已在本地 FHIR 服务器上更新。

通过执行以下步骤,您已成功实现一种机制来修改患者数据,并将更改同步到您的 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 库中使用 Sync API、Data Access API 和 Search API

后续步骤

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

了解详情