1. 始める前に
作成するアプリの概要
この Codelab では、FHIR エンジン ライブラリを使用して Android アプリを作成します。アプリは FHIR Engine ライブラリを使用して、FHIR サーバーから FHIR リソースをダウンロードし、ローカルの変更をサーバーにアップロードします。
学習内容
- Docker を使用してローカル HAPI FHIR サーバーを作成する方法
- FHIR Engine ライブラリを Android アプリに統合する方法
- Sync API を使用して、FHIR リソースのダウンロードとアップロードを行う 1 回限りまたは定期的なジョブを設定する方法
- Search API の使用方法
- Data Access 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 サーバーを設定する
- ターミナルで次のコマンドを実行して、HAPI FHIR の最新イメージを取得します。
docker pull hapiproject/hapi:latest
- Docker デスクトップを使用して以前にダウンロードしたイメージ
hapiproject/hapi
を実行するか、次のコマンドを実行して HAPI FHIR コンテナを作成します
詳しくはこちらをご覧ください。docker run -p 8080:8080 hapiproject/hapi:latest
- ブラウザで URL
http://localhost:8080/
を開き、サーバーを検査します。HAPI FHIR ウェブ インターフェースが表示されます。
ローカル HAPI FHIR サーバーにテストデータを入力する
アプリケーションをテストするには、サーバー上にテストデータが必要です。ここでは Synthea で生成された合成データを使用します。
- まず、synthea-samples からサンプルデータをダウンロードする必要があります。
synthea_sample_data_fhir_r4_sep2019.zip
をダウンロードして解凍します。解凍したサンプルデータには多数の.json
ファイルがあり、それぞれが個々の患者のトランザクション バンドルになっています。 - 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/
- すべての患者の検査データをサーバーにアップロードするには、
を実行します。 ただし、完了までに時間がかかることがあるため、この Codelab では不要です。for f in *.json; do curl -X POST -H "Content-Type: application/json" -d @$f http://localhost:8080/fhir/ ; done
- ブラウザで 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/
フォルダを選択します。
プロジェクトと 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]()を選択します。また、アプリを再度実行して、依存関係が正しく動作していることを確認します。
スターター アプリを実行する
これで Android Studio にプロジェクトがインポートされたので、アプリを初めて実行する準備が整いました。
Android Studio エミュレータを起動し、Android Studio ツールバーの実行アイコン()をクリックします。
4. FHIR エンジン インスタンスの作成
FHIR エンジンを Android アプリに組み込むには、FHIR エンジン ライブラリを使用して FHIR エンジンのインスタンスを起動する必要があります。以下に、このプロセスのガイドを示します。
app/src/main/java/com/google/android/fhir/codelabs/engine
にある Application クラスに移動します。この例では、FhirApplication.kt
です。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
: データベースのエラー戦略を決定します。この場合、開くときにエラーが発生するとデータベースが再作成されます。ServerConfiguration
のbaseUrl
: これは FHIR サーバーのベース URL です。指定された IP アドレス10.0.2.2
は、Android Emulator からアクセスできる localhost 用に特別に予約されています。詳細
FhirApplication
クラスに次の行を追加して、FHIR エンジンを遅延インスタンス化します。
これにより、アプリの起動直後ではなく、初回アクセス時にのみ FhirEngine インスタンスが作成されます。private val fhirEngine: FhirEngine by lazy { FhirEngineProvider.getInstance(this) }
FhirApplication
クラスに次の便利なメソッドを追加して、アプリ全体で簡単にアクセスできるようにします。
この静的メソッドを使用すると、コンテキストを使用して、アプリ内のどこからでも FHIR エンジン インスタンスを取得できます。companion object { fun fhirEngine(context: Context) = (context.applicationContext as FhirApplication).fhirEngine }
5. FHIR サーバーとのデータの同期
- 新しいクラス
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 } }
- 新しいクラス
AppFhirSyncWorker.kt
を作成します。このクラスは、バックグラウンド ワーカーを使用してアプリをリモート FHIR サーバーと同期する方法を定義します。
ここでは、同期に使用するダウンロード マネージャー、競合リゾルバ、FHIR エンジン インスタンスを定義しました。class AppFhirSyncWorker(appContext: Context, workerParams: WorkerParameters) : FhirSyncWorker(appContext, workerParams) { override fun getDownloadWorkManager() = DownloadWorkManagerImpl() override fun getConflictResolver() = AcceptLocalConflictResolver override fun getFhirEngine() = FhirApplication.fhirEngine(applicationContext) }
- ViewModel(
PatientListViewModel.kt
)で、1 回限りの同期メカニズムを設定します。次のコードを見つけてtriggerOneTimeSync()
関数に追加します。
このコルーチンは、前に定義した AppFhirSyncWorker を使用して FHIR サーバーとの 1 回限りの同期を開始します。その後、同期プロセスの状態に基づいて UI を更新します。viewModelScope.launch { Sync.oneTimeSync<AppFhirSyncWorker>(getApplication()) .shareIn(this, SharingStarted.Eagerly, 10) .collect { _pollState.emit(it) } }
PatientListFragment.kt
ファイルで、handleSyncJobStatus
関数の本文を更新します。
ここでは、同期プロセスが終了すると、ユーザーに通知するトースト メッセージが表示されます。その後、アプリは空の名前で検索を呼び出し、すべての患者を表示します。when (syncJobStatus) { is SyncJobStatus.Finished -> { Toast.makeText(requireContext(), "Sync Finished", Toast.LENGTH_SHORT).show() viewModel.searchPatientsByName("") } else -> {} }
すべての設定が完了したので、アプリを実行します。メニューの Sync
ボタンをクリックします。すべてが正しく機能すれば、ローカル FHIR サーバーから患者がダウンロードされてアプリケーションに表示されます。
6. 患者データの変更とアップロード
このセクションでは、特定の基準に基づいて患者データを変更し、更新されたデータを FHIR サーバーにアップロードするプロセスを説明します。具体的には、Wakefield
と Taunton
にお住まいの患者の住所の都市を入れ替えます。
ステップ 1: PatientListViewModel で変更ロジックを設定する
このセクションのコードは、PatientListViewModel
の triggerUpdate
関数に追加されています。
- FHIR エンジンにアクセスする:まず、
PatientListViewModel.kt
で FHIR エンジンへの参照を取得します。
このコードは、ViewModel のスコープ内でコルーチンを起動し、FHIR エンジンを初期化します。viewModelScope.launch { val fhirEngine = FhirApplication.fhirEngine(getApplication())
- ウェークフィールドからの患者の検索:FHIR エンジンを使用して、住所の都市が
Wakefield
の患者を検索します。
ここでは、FHIR エンジンのval patientsFromWakefield = fhirEngine.search<Patient> { filter( Patient.ADDRESS_CITY, { modifier = StringFilterModifier.MATCHES_EXACTLY value = "Wakefield" } ) }
search
メソッドを使用して、住所の都市に基づいて患者をフィルタします。その結果、ウェークフィールドの患者リストが作成されます。 - トーントンの患者を検索する:同様に、住所の都市が
Taunton
の患者を検索します。
現在、患者のリストが 2 つあります。1 つはウェイクフィールドの患者リスト、もう 1 つはトーントンの患者リストです。val patientsFromTaunton = fhirEngine.search<Patient> { filter( Patient.ADDRESS_CITY, { modifier = StringFilterModifier.MATCHES_EXACTLY value = "Taunton" } ) }
- 患者の住所の変更:
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) }
- 同期の開始:データをローカルで変更した後、1 回限りの同期をトリガーして、FHIR サーバー上でデータが更新されるようにします。
右中かっこtriggerOneTimeSync() }
}
は、開始時に開始されたコルーチンの終了を示します。
ステップ 2: 機能をテストする
- UI テスト:アプリを実行します。メニューの [
Update
] ボタンをクリックします。患者Aaron697
とAbby752
の住所の都市が入れ替わっています。 - サーバーの確認:ブラウザを開いて
http://localhost:8080/fhir/Patient/
に移動します。患者Aaron697
とAbby752
の住所の都市がローカル 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: 新しい検索機能をテストする
- アプリを再起動する:これらの変更を行った後、アプリを再ビルドして実行します。
- 患者の検索: 患者リスト画面で検索機能を使用します。名前(または名前の一部)を入力して、患者のリストをフィルタリングできるようになりました。
ここまでのステップを完了し、ユーザーが名前で患者を効率的に検索できるようにすることで、アプリケーションの機能を強化しました。これにより、ユーザー エクスペリエンスとデータ取得の効率が大幅に向上します。
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 ライブラリを適用する