Administra recursos de FHIR con la biblioteca de FHIR Engine

1. Antes de comenzar

Qué compilarás

En este codelab, compilarás una app para Android con la biblioteca de FHIR Engine. Tu app usará la biblioteca de FHIR Engine para descargar recursos de FHIR desde un servidor de FHIR y subir los cambios locales al servidor.

Qué aprenderás

  • Cómo crear un servidor HAPI FHIR local con Docker
  • Cómo integrar la biblioteca de FHIR Engine en tu aplicación para Android
  • Cómo usar la API de Sync para configurar un trabajo único o periódico para descargar y subir recursos de FHIR
  • Cómo usar la API de Search
  • Cómo usar las APIs de acceso a los datos para crear, leer, actualizar y borrar recursos de FHIR de forma local

Requisitos

Si nunca antes compilaste apps para Android, puedes comenzar por crear tu primera app.

2. Configura un servidor HAPI FHIR local con datos de prueba

HAPI FHIR es un servidor de FHIR de código abierto popular. En nuestro codelab, usamos un servidor HAPI FHIR local al que se puede conectar la app para Android.

Configura el servidor local de HAPI FHIR

  1. Ejecuta el siguiente comando en una terminal para obtener la imagen más reciente de HAPI FHIR.
    docker pull hapiproject/hapi:latest
    
  2. Crea un contenedor de HAPI FHIR con Docker Desktop para ejecutar la imagen hapiproject/hapi que descargaste anteriormente o ejecuta el siguiente comando:
    docker run -p 8080:8080 hapiproject/hapi:latest
    
    Obtén más información.
  3. Para inspeccionar el servidor, abre la URL http://localhost:8080/ en un navegador. Deberías ver la interfaz web de HAPI FHIR.Interfaz web de HAPI FHIR

Cómo propagar el servidor local de HAPI FHIR con datos de prueba

Para probar nuestra aplicación, necesitaremos algunos datos de prueba en el servidor. Usaremos los datos sintéticos que genera Synthea.

  1. Primero, debemos descargar datos de muestra de synthea-samples. Descarga y extrae synthea_sample_data_fhir_r4_sep2019.zip. Los datos de muestra sin comprimir tienen numerosos archivos .json, cada uno de los cuales es un paquete de transacciones para un paciente individual.
  2. Subiremos datos de prueba de tres pacientes al servidor local de HAPI FHIR. Ejecuta el siguiente comando en el directorio que contiene los archivos 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. Para subir datos de prueba de todos los pacientes al servidor, ejecuta
    for f in *.json; do curl -X POST -H "Content-Type: application/json" -d @$f http://localhost:8080/fhir/ ; done
    
    Sin embargo, esto puede tardar mucho tiempo en completarse y no es necesario para el codelab.
  4. Abre la URL http://localhost:8080/fhir/Patient/ en un navegador para verificar que los datos de prueba estén disponibles en el servidor. Deberías ver el texto HTTP 200 OK y la sección Response Body de la página que contiene los datos del paciente en un paquete de FHIR como el resultado de la búsqueda con un recuento de total.Prueba los datos en el servidor

3. Configura la app para Android

Cómo descargar el código

Para descargar el código de este codelab, clona el repositorio del SDK de Android FHIR: git clone https://github.com/google/android-fhir.git

El proyecto inicial de este codelab se encuentra en codelabs/engine.

Importa la app a Android Studio

Para comenzar, importamos la app de partida a Android Studio.

Abre Android Studio, selecciona Import Project (Gradle, Eclipse ADT, etc.) y elige la carpeta codelabs/engine/ del código fuente que descargaste antes.

Pantalla de inicio de Android Studio

Sincroniza tu proyecto con archivos de Gradle

Para tu comodidad, las dependencias de la biblioteca de FHIR Engine ya se agregaron al proyecto. Esto te permite integrar la biblioteca de FHIR Engine en tu app. Observa las siguientes líneas hasta el final del archivo app/build.gradle.kts de tu proyecto:

dependencies {
    // ...

    implementation("com.google.android.fhir:engine:1.1.0")
}

Para asegurarte de que todas las dependencias estén disponibles para tu app, debes sincronizar tu proyecto con los archivos Gradle en este punto.

Selecciona Sync Project with Gradle Files (Botón de sincronización de Gradle) en la barra de herramientas de Android Studio. También puedes volver a ejecutar la app para verificar que las dependencias funcionen correctamente.

Ejecuta la app de partida

Ahora que importaste el proyecto a Android Studio, tienes todo listo para ejecutar la app por primera vez.

Inicia el emulador de Android Studio y haz clic en Run (Botón Ejecutar) en la barra de herramientas de Android Studio.

App de Hello World

4. Crea una instancia de FHIR Engine

Para incorporar el motor de FHIR en tu app para Android, deberás usar la biblioteca de FHIR Engine e iniciar una instancia del motor de FHIR. Los pasos que se describen a continuación te guiarán a través del proceso.

  1. Navega a tu clase Application, que en este ejemplo es FhirApplication.kt, ubicada en app/src/main/java/com/google/android/fhir/codelabs/engine.
  2. Dentro del método onCreate(), agrega el siguiente código para inicializar 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)
                },
            ),
          ),
      )
    
    Notas:
    • enableEncryptionIfSupported: Habilita la encriptación de datos si el dispositivo la admite.
    • RECREATE_AT_OPEN: Determina la estrategia de error de la base de datos. En este caso, se vuelve a crear la base de datos si se produce un error cuando se abre.
    • baseUrl en ServerConfiguration: Es la URL base del servidor de FHIR. La dirección IP proporcionada 10.0.2.2 está reservada especialmente para localhost, a la que se puede acceder desde el emulador de Android. Obtén más información.
  3. En la clase FhirApplication, agrega la siguiente línea para crear una instancia del motor de FHIR de forma diferida:
      private val fhirEngine: FhirEngine by
          lazy { FhirEngineProvider.getInstance(this) }
    
    Esto garantiza que la instancia de FhirEngine solo se cree cuando se acceda a ella por primera vez, no de inmediato cuando se inicie la app.
  4. Agrega el siguiente método de conveniencia en la clase FhirApplication para facilitar el acceso en toda la aplicación:
    companion object {
        fun fhirEngine(context: Context) =
            (context.applicationContext as FhirApplication).fhirEngine
    }
    
    Este método estático te permite recuperar la instancia de FHIR Engine desde cualquier lugar de la app con el contexto.

5. Sincroniza datos con el servidor de FHIR

  1. Crea una clase nueva DownloadWorkManagerImpl.kt. En esta clase, definirás cómo la aplicación recupera el siguiente recurso de la lista para descargarlo.:
      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
        }
      }
    
    Esta clase tiene una fila de tipos de recursos que desea descargar. Procesa las respuestas y extrae los recursos del paquete que se muestra, que se guardan en la base de datos local.
  2. Crea una clase nueva AppFhirSyncWorker.kt. Esta clase define cómo se sincronizará la app con el servidor remoto de FHIR mediante un trabajador en segundo plano.
    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,
        )
    }
    
    Aquí, definimos qué administrador de descargas, solucionador de conflictos y instancia del motor de FHIR se usarán para la sincronización.
  3. En tu ViewModel, PatientListViewModel.kt, configurarás un mecanismo de sincronización único. Busca y agrega este código a la función triggerOneTimeSync():
    viewModelScope.launch {
          Sync.oneTimeSync<AppFhirSyncWorker>(getApplication())
            .shareIn(this, SharingStarted.Eagerly, 10)
            .collect { _pollState.emit(it) }
        }
    
    Esta corrutina inicia una sincronización única con el servidor de FHIR mediante AppFhirSyncWorker que definimos antes. Luego, actualizará la IU según el estado del proceso de sincronización.
  4. En el archivo PatientListFragment.kt, actualiza el cuerpo de la función handleSyncJobStatus:
    when (syncJobStatus) {
        is SyncJobStatus.Finished -> {
            Toast.makeText(requireContext(), "Sync Finished", Toast.LENGTH_SHORT).show()
            viewModel.searchPatientsByName("")
        }
        else -> {}
    }
    
    Aquí, cuando finalice el proceso de sincronización, se mostrará un mensaje emergente para notificar al usuario y, luego, la app mostrará a todos los pacientes invocando una búsqueda con un nombre vacío.

Ahora que todo está configurado, ejecuta la app. Haz clic en el botón Sync del menú. Si todo funciona correctamente, deberías ver que los pacientes de tu servidor FHIR local se descargan y se muestran en la aplicación.

Lista de pacientes

6. Modifica y sube datos de pacientes

En esta sección, te guiaremos por el proceso de modificación de los datos de los pacientes en función de criterios específicos y la carga de los datos actualizados a tu servidor de FHIR. Específicamente, intercambiaremos las ciudades de las direcciones de los pacientes que residen en Wakefield y Taunton.

Paso 1: Configura la lógica de modificación en PatientListViewModel

El código de esta sección se agrega a la función triggerUpdate en PatientListViewModel.

  1. Accede al motor de FHIR:Comienza por obtener una referencia al motor de FHIR en PatientListViewModel.kt.
    viewModelScope.launch {
       val fhirEngine = FhirApplication.fhirEngine(getApplication())
    
    Este código inicia una corrutina dentro del alcance de ViewModel y, luego, inicializa el motor de FHIR.
  2. Buscar pacientes de Wakefield:Usa el motor de FHIR para buscar pacientes con una ciudad de dirección de Wakefield.
    val patientsFromWakefield =
         fhirEngine.search<Patient> {
           filter(
             Patient.ADDRESS_CITY,
             {
               modifier =  StringFilterModifier.MATCHES_EXACTLY
               value = "Wakefield"
             }
           )
         }
    
    Aquí, usamos el método search del motor de FHIR para filtrar pacientes según la ciudad de su dirección. El resultado será una lista de pacientes de Wakefield.
  3. Buscar pacientes de Taunton:Del mismo modo, busca pacientes con una ciudad de dirección de Taunton.
    val patientsFromTaunton =
         fhirEngine.search<Patient> {
           filter(
             Patient.ADDRESS_CITY,
             {
               modifier =  StringFilterModifier.MATCHES_EXACTLY
               value = "Taunton"
             }
           )
         }
    
    Ahora tenemos dos listas de pacientes: una de Wakefield y la otra de Taunton.
  4. Modifica las direcciones de los pacientes:Revisa cada paciente de la lista patientsFromWakefield, cambia su ciudad a Taunton y actualízalo en el motor de FHIR.
    patientsFromWakefield.forEach {
         it.resource.address.first().city = "Taunton"
         fhirEngine.update(it.resource)
    }
    
    Del mismo modo, actualiza cada paciente de la lista patientsFromTaunton para que su ciudad cambie a Wakefield.
    patientsFromTaunton.forEach {
         it.resource.address.first().city = "Wakefield"
         fhirEngine.update(it.resource)
    }
    
  5. Iniciar sincronización:Después de modificar los datos de forma local, activa una sincronización única para asegurarte de que se actualicen en el servidor de FHIR.
    triggerOneTimeSync()
    }
    
    La llave de cierre } indica el final de la corrutina que se inició al principio.

Paso 2: Prueba la funcionalidad

  1. Pruebas de IU:Ejecuta la app. Haz clic en el botón Update del menú. Deberías ver que las ciudades de las direcciones de los pacientes Aaron697 y Abby752 están intercambiadas.
  2. Verificación del servidor:Abre un navegador y navega a http://localhost:8080/fhir/Patient/. Verifica que la ciudad de la dirección de los pacientes Aaron697 y Abby752 esté actualizada en el servidor local de FHIR.

Si sigues estos pasos, habrás implementado correctamente un mecanismo para modificar los datos de los pacientes y sincronizar los cambios con tu servidor de FHIR.

7. Buscar pacientes por nombre

Buscar pacientes por sus nombres puede proporcionar una forma fácil de recuperar información. Aquí, te guiaremos en el proceso de implementación de esta función en tu aplicación.

Paso 1: Actualiza la firma de la función

Navega a tu archivo PatientListViewModel.kt y busca la función llamada searchPatientsByName. Agregaremos código a esta función.

Para filtrar los resultados según la búsqueda de nombre proporcionada y emitir los resultados para que la IU los actualice, incorpora el siguiente bloque de código condicional:

    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 }
      }
    }

Aquí, si nameQuery no está vacío, la función de búsqueda filtrará los resultados para incluir solo a los pacientes cuyos nombres contengan la búsqueda especificada.

Paso 2: Prueba la nueva funcionalidad de búsqueda

  1. Reinicia la app:Después de realizar estos cambios, vuelve a compilar y ejecutar la app.
  2. Buscar pacientes: En la pantalla de la lista de pacientes, usa la función de búsqueda. Ahora deberías poder ingresar un nombre (o parte de un nombre) para filtrar la lista de pacientes según corresponda.

Una vez que hayas completado estos pasos, habrás mejorado tu aplicación, ya que les brindarás a los usuarios la capacidad de buscar pacientes de forma eficiente por sus nombres. Esto puede mejorar significativamente la experiencia del usuario y la eficiencia en la recuperación de datos.

8. ¡Felicitaciones!

Usaste la biblioteca de FHIR Engine para administrar recursos de FHIR en tu app:

  • Usa la API de Sync para sincronizar recursos de FHIR con un servidor de FHIR
  • Usa la API de Data Access para crear, leer, actualizar y borrar recursos FHIR locales
  • Usa la API de Search para buscar recursos de FHIR locales

Temas abordados

  • Cómo configurar un servidor local de HAPI FHIR
  • Cómo subir datos de prueba al servidor local de HAPI FHIR
  • Cómo compilar una app para Android con la biblioteca de FHIR Engine
  • Cómo usar la API de Sync, la API de Data Access y la API de Search en la biblioteca de FHIR Engine

Próximos pasos

  • Explora la documentación de la biblioteca de FHIR Engine
  • Explora las funciones avanzadas de la API de Búsqueda
  • Aplica la biblioteca de FHIR Engine en tu propia app para Android

Más información