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

1. 始める前に

作成するアプリの概要

この Codelab では、FHIR エンジン ライブラリを使用して Android アプリを作成します。アプリは FHIR Engine ライブラリを使用して、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 デスクトップを使用して以前にダウンロードしたイメージ 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/ を開き、テストデータがサーバー上で利用可能であることを確認します。FHIR バンドルの患者データを含むページの「HTTP 200 OK」と「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 エンジン ライブラリの依存関係はすでにプロジェクトに追加されています。これにより、FHIR エンジン ライブラリをアプリに統合できます。プロジェクトの 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 ツールバーの実行アイコン([実行] ボタン)をクリックします。

Hello World アプリ

4. FHIR エンジン インスタンスの作成

FHIR エンジンを Android アプリに組み込むには、FHIR エンジン ライブラリを使用して FHIR エンジンのインスタンスを起動する必要があります。以下に、このプロセスのガイドを示します。

  1. app/src/main/java/com/google/android/fhir/codelabs/engine にある Application クラスに移動します。この例では、FhirApplication.kt です。
  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: データベースのエラー戦略を決定します。この場合、開くときにエラーが発生するとデータベースが再作成されます。
    • ServerConfigurationbaseUrl: これは FHIR サーバーのベース URL です。指定された IP アドレス 10.0.2.2 は、Android Emulator からアクセスできる localhost 用に特別に予約されています。詳細
  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 エンジン インスタンスを取得できます。

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)
    }
    
    ここでは、同期に使用するダウンロード マネージャー、競合リゾルバ、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 の患者を検索します。
    val patientsFromTaunton =
         fhirEngine.search<Patient> {
           filter(
             Patient.ADDRESS_CITY,
             {
               modifier =  StringFilterModifier.MATCHES_EXACTLY
               value = "Taunton"
             }
           )
         }
    
    現在、患者のリストが 2 つあります。1 つはウェイクフィールドの患者リスト、もう 1 つはトーントンの患者リストです。
  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 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 エンジン ライブラリのドキュメントを確認する
  • Search API の高度な機能を確認する
  • 独自の Android アプリに FHIR Engine ライブラリを適用する

詳細