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

1. 始める前に

作成するアプリの概要

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

学習内容

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

必要なもの

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/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 ツールバーの [実行]([実行] ボタン)をクリックします。

Hello World アプリ

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

FHIR Engine を Android アプリに組み込むには、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: データベース エラー戦略を決定します。この場合、開くときにエラーが発生すると、データベースが再作成されます。
    • ServerConfigurationbaseUrl: FHIR サーバーのベース URL です。指定された 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)
    
      override fun getUploadStrategy() =
        UploadStrategy.forBundleRequest(
          methodForCreate = HttpCreateMethod.PUT,
          methodForUpdate = HttpUpdateMethod.PATCH,
          squash = true,
          bundleSize = 500,
        )
    }
    
    ここでは、同期に使用するダウンロード マネージャー、競合解決ツール、FHIR エンジン インスタンスを定義しています。
  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 にアクセスする:まず、PatientListViewModel.kt で FHIR Engine への参照を取得します。
    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"
             }
           )
         }
    
    これで、ウェイクフィールドとトーントンの 2 つの患者リストができました。
  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. 同期を開始する:データをローカルで変更した後、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 エンジン ライブラリを使用して、アプリで 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 ライブラリを適用する

詳細