1. 시작하기 전에
빌드할 항목
이 Codelab에서는 FHIR 엔진 라이브러리를 사용하여 Android 앱을 빌드합니다. 앱은 FHIR 엔진 라이브러리를 사용하여 FHIR 서버에서 FHIR 리소스를 다운로드하고 로컬 변경사항을 서버에 업로드합니다.
학습할 내용
- Docker를 사용하여 로컬 HAPI FHIR 서버를 만드는 방법
- FHIR 엔진 라이브러리를 Android 애플리케이션에 통합하는 방법
- Sync API를 사용하여 FHIR 리소스를 다운로드 및 업로드하는 일회성 또는 주기적 작업을 설정하는 방법
- Search API 사용 방법
- Data Access API를 사용하여 FHIR 리소스를 로컬에서 생성, 읽기, 업데이트, 삭제하는 방법
필요한 항목
- Docker (Docker 가져오기)
- 최신 버전의 Android 스튜디오 (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파일이 많이 있습니다. - 로컬 HAPI FHIR 서버에 환자 3명의 테스트 데이터를 업로드합니다. 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 스튜디오로 앱 가져오기
먼저 시작 앱을 Android 스튜디오로 가져옵니다.
Android 스튜디오를 열고 Import Project (Gradle, Eclipse ADT, etc.)를 선택한 다음 이전에 다운로드한 소스 코드에서 codelabs/engine/ 폴더를 선택합니다.

프로젝트를 Gradle 파일과 동기화
편의를 위해 FHIR 엔진 라이브러리 종속 항목이 이미 프로젝트에 추가되어 있습니다. 이렇게 하면 앱에 FHIR 엔진 라이브러리를 통합할 수 있습니다. 프로젝트의 app/build.gradle.kts 파일 끝까지 다음 줄을 살펴보세요.
dependencies {
// ...
implementation("com.google.android.fhir:engine:1.1.0")
}
앱에서 모든 종속 항목을 사용할 수 있도록 하려면 이 시점에서 프로젝트를 Gradle 파일과 동기화해야 합니다.
Android 스튜디오 툴바에서 Sync Project with Gradle Files(Gradle 파일과 프로젝트 동기화)(
)를 선택합니다. 앱을 다시 실행하여 종속 항목이 올바르게 작동하는지 확인할 수도 있습니다.
시작 앱 실행
이제 프로젝트를 Android 스튜디오로 가져왔으므로 앱을 처음으로 실행할 수 있습니다.
Android 스튜디오 에뮬레이터를 시작하고 Android 스튜디오 툴바에서 실행 (
)을 클릭합니다.

4. FHIR 엔진 인스턴스 만들기
FHIR 엔진을 Android 앱에 통합하려면 FHIR 엔진 라이브러리를 사용하고 FHIR 엔진의 인스턴스를 시작해야 합니다. 아래 단계에 따라 절차를 진행하세요.
- 이 예에서는
app/src/main/java/com/google/android/fhir/codelabs/engine에 있는FhirApplication.kt인 Application 클래스로 이동합니다. 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 에뮬레이터에서 액세스할 수 있는 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) override fun getUploadStrategy() = UploadStrategy.forBundleRequest( methodForCreate = HttpCreateMethod.PUT, methodForUpdate = HttpUpdateMethod.PATCH, squash = true, bundleSize = 500, ) } - ViewModel(
PatientListViewModel.kt)에서 일회성 동기화 메커니즘을 설정합니다. 다음 코드를 찾아triggerOneTimeSync()함수에 추가합니다. 이 코루틴은 앞에서 정의한 AppFhirSyncWorker를 사용하여 FHIR 서버와 일회성 동기화를 시작합니다. 그런 다음 동기화 프로세스의 상태에 따라 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()) - Wakefield의 환자 검색:FHIR 엔진을 사용하여 주소 도시가
Wakefield인 환자를 검색합니다. 여기서는 FHIR 엔진의val patientsFromWakefield = fhirEngine.search<Patient> { filter( Patient.ADDRESS_CITY, { modifier = StringFilterModifier.MATCHES_EXACTLY value = "Wakefield" } ) }search메서드를 사용하여 주소 도시를 기준으로 환자를 필터링합니다. 결과는 웨이크필드의 환자 목록입니다. - 타운턴 환자 검색:마찬가지로 주소 도시가
Taunton인 환자를 검색합니다. 이제 웨이크필드와 타운턴의 환자 목록이 각각 하나씩 있습니다.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) } - 동기화 시작:로컬에서 데이터를 수정한 후 일회성 동기화를 트리거하여 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 엔진 라이브러리에서 Sync API, Data Access API, Search API를 사용하는 방법
다음 단계
- FHIR 엔진 라이브러리 문서 살펴보기
- Search API의 고급 기능 살펴보기
- 자체 Android 앱에 FHIR 엔진 라이브러리 적용