FHIR エンジン ライブラリを使用して FHIR リソースを管理する

1. 始める前に

作成するアプリの概要

この Codelab では、FHIR Engine ライブラリを使用して Android アプリを作成します。アプリは FHIR Engine ライブラリを使用して、FHIR サーバーから FHIR リソースをダウンロードし、ローカルでの変更をサーバーにアップロードします。

学習内容

  • Docker を使用してローカル HAPI FHIR サーバーを作成する方法
  • FHIR Engine ライブラリを Android アプリに統合する方法
  • Sync API を使用して、FHIR リソースをダウンロードしてアップロードする 1 回限りのジョブまたは定期的なジョブを設定する方法
  • Search API の使用方法
  • データアクセス API を使用して、FHIR リソースをローカルで作成、読み取り、更新、削除する方法

必要なもの

  • Docker(Docker を入手
  • Android Studio の最新バージョン(v4.1.2 以降)
  • Android Emulatorまたは Android 7.0 Nougat 以降を搭載した物理 Android デバイス
  • サンプルコード
  • Kotlin での Android 開発に関する基本的な知識

Android アプリを作成したことがない場合は、最初のアプリを作成することから始めます。

2. テストデータを使用してローカル HAPI FHIR サーバーを設定する

HAPI FHIR は、よく使用されているオープンソースの FHIR サーバーです。この Codelab では、Android アプリが接続するローカル HAPI FHIR サーバーを使用します。

ローカル 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. ブラウザで URL http://localhost:8080/ を開いて、サーバーを検査します。HAPI FHIR ウェブ インターフェースが表示されます。HAPI FHIR ウェブ インターフェース

テストデータをローカル HAPI FHIR サーバーに入力する

アプリをテストするには、サーバーにテストデータが必要です。Synthea によって生成された合成データを使用します。

  1. まず、synthea-samples からサンプルデータをダウンロードする必要があります。synthea_sample_data_fhir_r4_sep2019.zip をダウンロードして展開します。展開されたサンプルデータには多数の .json ファイルがあり、それぞれが個々の患者のトランザクション バンドルです。
  2. 3 人の患者のテストデータをローカル 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. ブラウザで URL http://localhost:8080/fhir/Patient/ を開いて、テストデータがサーバーで使用可能であることを確認します。HTTP 200 OK というテキストと、検索結果として FHIR バンドルに患者データを含むページの Response Body セクションが表示され、total カウントが表示されます。サーバー上のテストデータ

3. Android アプリを設定する

コードをダウンロードする

この Codelab のコードをダウンロードするには、Android FHIR SDK リポジトリのクローンを作成します。git clone https://github.com/ohs-foundation/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 のツールバーで [実行]([実行] ボタン)をクリックします。

Hello World アプリ

4. FHIR Engine インスタンスを作成する

FHIR Engine を Android アプリに組み込むには、FHIR Engine ライブラリを使用して FHIR Engine のインスタンスを開始する必要があります。以下の手順に沿って操作してください。

  1. Application クラスに移動します。この例では、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: データベース エラーの戦略を決定します。この場合、開くときにエラーが発生するとデータベースが再作成されます。
    • ServerConfigurationbaseUrl: これは FHIR サーバーのベース URL です。指定された IP アドレス 10.0.2.2 は、Android エミュレータからアクセスできる localhost 用に特別に予約されています。詳細を学ぶ
  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 Engine インスタンスを定義しました。
  3. ViewModel PatientListViewModel.kt で、1 回限りの同期メカニズムを設定します。このコードを見つけて、triggerOneTimeSync() 関数に追加します。
    viewModelScope.launch {
          Sync.oneTimeSync<AppFhirSyncWorker>(getApplication())
            .shareIn(this, SharingStarted.Eagerly, 10)
            .collect { _pollState.emit(it) }
        }
    
    このコルーチンは、先ほど定義した AppFhirSyncWorker を使用して、FHIR サーバーとの 1 回限りの同期を開始します。同期プロセスの状態に基づいて 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 Engine にアクセスする:まず、FHIR Engine への参照を PatientListViewModel.kt で取得します。
    viewModelScope.launch {
       val fhirEngine = FhirApplication.fhirEngine(getApplication())
    
    このコードは、ViewModel のスコープ内でコルーチンを起動し、FHIR Engine を初期化します。
  2. Wakefield の患者を検索する:FHIR Engine を使用して、住所の市区町村が Wakefield の患者を検索します。
    val patientsFromWakefield =
         fhirEngine.search<Patient> {
           filter(
             Patient.ADDRESS_CITY,
             {
               modifier =  StringFilterModifier.MATCHES_EXACTLY
               value = "Wakefield"
             }
           )
         }
    
    ここでは、FHIR Engine の search メソッドを使用して、住所の市区町村に基づいて患者をフィルタリングしています。結果は、Wakefield の患者のリストになります。
  3. Taunton の患者を検索する:同様に、住所の市区町村が Taunton の患者を検索します。
    val patientsFromTaunton =
         fhirEngine.search<Patient> {
           filter(
             Patient.ADDRESS_CITY,
             {
               modifier =  StringFilterModifier.MATCHES_EXACTLY
               value = "Taunton"
             }
           )
         }
    
    これで、Wakefield と Taunton の 2 つの患者リストができました。
  4. 患者の住所を変更する:patientsFromWakefield リストの各患者を調べて、市区町村を Taunton に変更し、FHIR Engine で更新します。
    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. 同期を開始する:データをローカルで変更したら、1 回限りの同期をトリガーして、FHIR サーバーでデータが更新されるようにします。
    triggerOneTimeSync()
    }
    
    閉じ中かっこ } は、最初に起動したコルーチンの終了を示します。

ステップ 2: 機能をテストする

  1. UI テスト:アプリを実行します。メニューの 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 の上級者向け機能を確認する
  • FHIR Engine ライブラリを独自の Android アプリに適用する

詳細