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

1. 始める前に

作成するアプリの概要

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

学習内容

  • Docker を使用してローカル HAPI FHIR サーバーを作成する方法
  • FHIR Engine ライブラリを Android アプリに統合する方法
  • Sync API を使用して、FHIR リソースのダウンロードとアップロードを行う 1 回限りまたは定期的なジョブを設定する方法
  • Search 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/ を開き、サーバーでテストデータが使用可能であることを確認します。検索結果として、total 個の患者データを含む FHIR バンドルのテキスト HTTP 200 OK とページの Response Body セクションが表示されます。サーバーでテストデータを使用する

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 エンジンを Android アプリに組み込むには、FHIR エンジン ライブラリを使用して FHIR エンジンのインスタンスを開始する必要があります。以下の手順に沿って、このプロセスを進めてください。

  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 エンジン インスタンスを定義しています。
  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 エンジンにアクセスする:まず、PatientListViewModel.kt で FHIR エンジンへの参照を取得します。
    viewModelScope.launch {
       val fhirEngine = FhirApplication.fhirEngine(getApplication())
    
    このコードは、ViewModel のスコープ内でコルーチンを起動し、FHIR エンジンを初期化します。
  2. ウェイクフィールドの患者を検索する:FHIR エンジンを使用して、住所の都市が Wakefield の患者を検索します。
    val patientsFromWakefield =
         fhirEngine.search<Patient> {
           filter(
             Patient.ADDRESS_CITY,
             {
               modifier =  StringFilterModifier.MATCHES_EXACTLY
               value = "Wakefield"
             }
           )
         }
    
    ここでは、FHIR エンジンの search メソッドを使用して、住所の市区町村に基づいて患者をフィルタしています。結果は、ウェイクフィールドの患者のリストになります。
  3. Taunton の患者を検索する:同様に、住所の市区町村が 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 エンジン ライブラリを使用して Android アプリを作成する方法
  • FHIR Engine ライブラリで Sync API、Data Access API、Search API を使用する方法

次のステップ

  • FHIR Engine ライブラリのドキュメントを確認する
  • Search API の高度な機能について
  • 独自の Android アプリに FHIR Engine ライブラリを適用する

詳細