1. 始める前に
作成するアプリの概要
この Codelab では、FHIR Engine ライブラリを使用して Android アプリを作成します。アプリは FHIR エンジン ライブラリを使用して、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 Desktop を使用して、前にダウンロードしたイメージ
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/
を開き、サーバーでテストデータが使用可能であることを確認します。検索結果として、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/
フォルダを選択します。
プロジェクトを 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]()を選択します。アプリをもう一度実行して、依存関係が正しく動作していることを確認することもできます。
スターター アプリを実行する
プロジェクトを Android Studio にインポートしたので、アプリを初めて実行する準備ができました。
Android Studio エミュレータを起動し、Android Studio ツールバーの実行アイコン()をクリックします。
4. FHIR Engine インスタンスを作成する
FHIR エンジンを Android アプリに組み込むには、FHIR エンジン ライブラリを使用して FHIR エンジンのインスタンスを開始する必要があります。以下の手順に沿って、このプロセスを進めてください。
- Application クラスに移動します。この例では、
app/src/main/java/com/google/android/fhir/codelabs/engine
にあるFhirApplication.kt
です。 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
: データベースのエラー戦略を決定します。この場合、開くときにエラーが発生すると、データベースが再作成されます。ServerConfiguration
のbaseUrl
: FHIR サーバーのベース URL です。指定された IP アドレス10.0.2.2
は、Android エミュレータからアクセスできる localhost 用に特別に予約されています。詳細
FhirApplication
クラスに次の行を追加して、FHIR Engine を遅延インスタンス化します。 これにより、FhirEngine インスタンスは、アプリの起動直後ではなく、初めてアクセスされたときにのみ作成されるようになります。private val fhirEngine: FhirEngine by lazy { FhirEngineProvider.getInstance(this) }
- アプリケーション全体で簡単にアクセスできるように、
FhirApplication
クラスに次の便利なメソッドを追加します。 この静的メソッドを使用すると、コンテキストを使用してアプリ内のどこからでも FHIR Engine インスタンスを取得できます。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) override fun getUploadStrategy() = UploadStrategy.forBundleRequest( methodForCreate = HttpCreateMethod.PUT, methodForUpdate = HttpUpdateMethod.PATCH, squash = true, bundleSize = 500, ) }
- 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 の患者を検索する:同様に、住所の市区町村が
Taunton
の患者を検索します。 これで、ウェイクフィールドとトーントンの 2 つの患者リストが作成されました。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 エンジン ライブラリを使用して、アプリ内の 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 ライブラリを適用する