Gerenciar recursos FHIR com a biblioteca FHIR Engine

1. Antes de começar

O que você vai criar

Neste codelab, você vai criar um app Android usando a biblioteca do FHIR Engine. Seu app vai usar a biblioteca FHIR Engine para fazer o download de recursos FHIR de um servidor FHIR e enviar mudanças locais para o servidor.

O que você vai aprender

  • Como criar um servidor HAPI FHIR local usando o Docker
  • Como integrar a biblioteca do FHIR Engine ao seu app Android
  • Como usar a API Sync para configurar um job único ou periódico para fazer download e upload de recursos do FHIR
  • Como usar a API Search
  • Como usar as APIs de acesso a dados para criar, ler, atualizar e excluir recursos do FHIR localmente

O que é necessário

Se você nunca criou apps Android, comece criando seu primeiro app.

2. Configurar um servidor HAPI FHIR local com dados de teste

O HAPI FHIR é um servidor FHIR de código aberto conhecido. Usamos um servidor HAPI FHIR local no nosso codelab para o app Android se conectar.

Configurar o servidor HAPI FHIR local

  1. Execute o seguinte comando em um terminal para receber a imagem mais recente do HAPI FHIR:
    docker pull hapiproject/hapi:latest
    
  2. Crie um contêiner HAPI FHIR usando o Docker Desktop para executar a imagem baixada anteriormente hapiproject/hapi ou executando o seguinte comando:
    docker run -p 8080:8080 hapiproject/hapi:latest
    
    Saiba mais.
  3. Abra o URL http://localhost:8080/ em um navegador para inspecionar o servidor. A interface da Web do HAPI FHIR vai aparecer.Interface da Web do HAPI FHIR

Preencher o servidor HAPI FHIR local com dados de teste

Para testar o aplicativo, precisamos de alguns dados de teste no servidor. Vamos usar dados sintéticos gerados pelo Synthea.

  1. Primeiro, faça o download dos dados de exemplo de synthea-samples. Faça o download e extraia synthea_sample_data_fhir_r4_sep2019.zip. Os dados de amostra descompactados têm vários arquivos .json, cada um sendo um pacote de transações para um paciente individual.
  2. Vamos fazer upload de dados de teste de três pacientes para o servidor HAPI FHIR local. Execute o comando a seguir no diretório que contém os arquivos 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 fazer upload dos dados de teste de todos os pacientes para o servidor, execute
    for f in *.json; do curl -X POST -H "Content-Type: application/json" -d @$f http://localhost:8080/fhir/ ; done
    
    No entanto, isso pode levar muito tempo para ser concluído e não é necessário para o codelab.
  4. Verifique se os dados de teste estão disponíveis no servidor abrindo o URL http://localhost:8080/fhir/Patient/ em um navegador. O texto HTTP 200 OK e a seção Response Body da página com dados de pacientes em um pacote FHIR vão aparecer como resultado da pesquisa com uma contagem de total.Dados de teste no servidor

3. Configurar o app Android

Fazer o download do código

Para fazer o download do código deste codelab, clone o repositório do SDK FHIR Android: git clone https://github.com/google/android-fhir.git

O projeto inicial para este codelab está localizado em codelabs/engine.

Importar o app para o Android Studio

Começamos importando o app inicial para o Android Studio.

Abra o Android Studio, selecione Import Project (Gradle, Eclipse ADT, etc.) e escolha a pasta codelabs/engine/ no código-fonte que você baixou anteriormente.

Tela inicial do Android Studio

Sincronizar o projeto com arquivos do Gradle

Para sua conveniência, as dependências da biblioteca do FHIR Engine já foram adicionadas ao projeto. Isso permite integrar a biblioteca FHIR Engine ao seu app. Observe as seguintes linhas até o final do arquivo app/build.gradle.kts do projeto:

dependencies {
    // ...

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

Para garantir que todas as dependências estejam disponíveis para seu app, sincronize o projeto com os arquivos do Gradle neste momento.

Selecione Sync Project with Gradle Files (Botão de sincronização do Gradle) na barra de ferramentas do Android Studio. Você também pode executar o app novamente para verificar se as dependências estão funcionando corretamente.

Executar o app inicial

Agora que você importou o projeto para o Android Studio, está tudo pronto para executar o app pela primeira vez.

Inicie o emulador do Android Studio e clique em Executar (Botão Executar) na barra de ferramentas do Android Studio.

App Hello World

4. Criar uma instância do FHIR Engine

Para incorporar o FHIR Engine ao seu app Android, use a biblioteca do FHIR Engine e inicie uma instância dele. As etapas descritas abaixo vão orientar você nesse processo.

  1. Navegue até a classe Application, que neste exemplo é FhirApplication.kt, localizada em app/src/main/java/com/google/android/fhir/codelabs/engine.
  2. No método onCreate(), adicione o seguinte código para inicializar o 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)
                },
            ),
          ),
      )
    
    Observações
      :
    • enableEncryptionIfSupported: ativa a criptografia de dados se o dispositivo for compatível.
    • RECREATE_AT_OPEN: determina a estratégia de erro do banco de dados. Nesse caso, ele recria o banco de dados se ocorrer um erro ao abrir.
    • baseUrl em ServerConfiguration: é o URL de base do servidor FHIR. O endereço IP 10.0.2.2 fornecido é reservado especialmente para localhost, acessível pelo emulador do Android. Saiba mais.
  3. Na classe FhirApplication, adicione a seguinte linha para instanciar o FHIR Engine de forma lenta:
      private val fhirEngine: FhirEngine by
          lazy { FhirEngineProvider.getInstance(this) }
    
    Isso garante que a instância do FhirEngine seja criada apenas quando for acessada pela primeira vez, e não imediatamente quando o app for iniciado.
  4. Adicione o seguinte método de conveniência à classe FhirApplication para facilitar o acesso em todo o aplicativo:
    companion object {
        fun fhirEngine(context: Context) =
            (context.applicationContext as FhirApplication).fhirEngine
    }
    
    Esse método estático permite recuperar a instância do FHIR Engine de qualquer lugar do app usando o contexto.

5. Sincronizar dados com o servidor FHIR

  1. Crie uma classe DownloadWorkManagerImpl.kt. Nesta classe, você vai definir como o aplicativo busca o próximo recurso da lista para fazer o download:
      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
        }
      }
    
    Essa classe tem uma fila de tipos de recursos que ela quer baixar. Ele processa respostas e extrai os recursos do pacote retornado, que são salvos no banco de dados local.
  2. Crie uma nova classe AppFhirSyncWorker.kt. Essa classe define como o app vai sincronizar com o servidor FHIR remoto usando um worker em 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,
        )
    }
    
    Aqui, definimos qual gerenciador de downloads, resolvedor de conflitos e instância do mecanismo FHIR usar para a sincronização.
  3. No ViewModel, PatientListViewModel.kt, você vai configurar um mecanismo de sincronização única. Localize e adicione este código à função triggerOneTimeSync():
    viewModelScope.launch {
          Sync.oneTimeSync<AppFhirSyncWorker>(getApplication())
            .shareIn(this, SharingStarted.Eagerly, 10)
            .collect { _pollState.emit(it) }
        }
    
    Essa corrotina inicia uma sincronização única com o servidor FHIR usando o AppFhirSyncWorker que definimos anteriormente. Em seguida, ele vai atualizar a interface com base no estado do processo de sincronização.
  4. No arquivo PatientListFragment.kt, atualize o corpo da função handleSyncJobStatus:
    when (syncJobStatus) {
        is SyncJobStatus.Finished -> {
            Toast.makeText(requireContext(), "Sync Finished", Toast.LENGTH_SHORT).show()
            viewModel.searchPatientsByName("")
        }
        else -> {}
    }
    
    Aqui, quando o processo de sincronização terminar, uma mensagem de toast será exibida notificando o usuário, e o app vai mostrar todos os pacientes invocando uma pesquisa com um nome vazio.

Agora que tudo está configurado, execute o app. Clique no botão Sync no menu. Se tudo funcionar corretamente, os pacientes do seu servidor FHIR local serão baixados e exibidos no aplicativo.

Lista de pacientes

6. Modificar e fazer upload de dados de pacientes

Nesta seção, vamos orientar você no processo de modificação dos dados do paciente com base em critérios específicos e no upload dos dados atualizados para o servidor FHIR. Especificamente, vamos trocar as cidades de endereço dos pacientes que moram em Wakefield e Taunton.

Etapa 1: configurar a lógica de modificação em PatientListViewModel

O código nesta seção é adicionado à função triggerUpdate em PatientListViewModel.

  1. Acessar o FHIR Engine:comece recebendo uma referência ao FHIR Engine no PatientListViewModel.kt.
    viewModelScope.launch {
       val fhirEngine = FhirApplication.fhirEngine(getApplication())
    
    Esse código inicia uma corrotina no escopo do ViewModel e inicializa o mecanismo FHIR.
  2. Pesquisar pacientes de Wakefield:use o mecanismo FHIR para pesquisar pacientes com uma cidade de endereço Wakefield.
    val patientsFromWakefield =
         fhirEngine.search<Patient> {
           filter(
             Patient.ADDRESS_CITY,
             {
               modifier =  StringFilterModifier.MATCHES_EXACTLY
               value = "Wakefield"
             }
           )
         }
    
    Aqui, estamos usando o método search do mecanismo FHIR para filtrar pacientes com base na cidade do endereço. O resultado será uma lista de pacientes de Wakefield.
  3. Pesquisar pacientes de Taunton:da mesma forma, pesquise pacientes com uma cidade de endereço Taunton.
    val patientsFromTaunton =
         fhirEngine.search<Patient> {
           filter(
             Patient.ADDRESS_CITY,
             {
               modifier =  StringFilterModifier.MATCHES_EXACTLY
               value = "Taunton"
             }
           )
         }
    
    Agora temos duas listas de pacientes: uma de Wakefield e outra de Taunton.
  4. Modificar endereços de pacientes:acesse cada paciente na lista patientsFromWakefield, mude a cidade para Taunton e atualize no mecanismo FHIR.
    patientsFromWakefield.forEach {
         it.resource.address.first().city = "Taunton"
         fhirEngine.update(it.resource)
    }
    
    Da mesma forma, atualize cada paciente na lista patientsFromTaunton para que a cidade seja alterada para Wakefield.
    patientsFromTaunton.forEach {
         it.resource.address.first().city = "Wakefield"
         fhirEngine.update(it.resource)
    }
    
  5. Iniciar sincronização:depois de modificar os dados localmente, acione uma sincronização única para garantir que os dados sejam atualizados no servidor FHIR.
    triggerOneTimeSync()
    }
    
    A chave de fechamento } indica o fim da corrotina iniciada no começo.

Etapa 2: testar a funcionalidade

  1. Teste de UI:execute o app. Clique no botão Update no menu. As cidades dos endereços dos pacientes Aaron697 e Abby752 vão aparecer trocadas.
  2. Verificação do servidor:abra um navegador e acesse http://localhost:8080/fhir/Patient/. Verifique se a cidade do endereço dos pacientes Aaron697 e Abby752 foi atualizada no servidor FHIR local.

Ao seguir estas etapas, você implementou um mecanismo para modificar os dados do paciente e sincronizar as mudanças com o servidor FHIR.

7. Pesquisar pacientes por nome

Pesquisar pacientes pelo nome pode ser uma maneira fácil de recuperar informações. Aqui, vamos mostrar o processo de implementação desse recurso no seu aplicativo.

Etapa 1: atualizar a assinatura da função

Navegue até o arquivo PatientListViewModel.kt e encontre a função chamada searchPatientsByName. Vamos adicionar código a essa função.

Para filtrar os resultados com base na consulta de nome fornecida e emitir os resultados para atualização da interface, incorpore o seguinte bloco 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 }
      }
    }

Aqui, se nameQuery não estiver vazio, a função de pesquisa vai filtrar os resultados para incluir apenas pacientes cujos nomes contenham a consulta especificada.

Etapa 2: testar a nova funcionalidade de pesquisa

  1. Reinicie o app:depois de fazer essas mudanças, recrie e execute o app.
  2. Pesquisar pacientes: na tela da lista de pacientes, use a funcionalidade de pesquisa. Agora você pode inserir um nome (ou parte dele) para filtrar a lista de pacientes.

Com essas etapas concluídas, você aprimorou seu aplicativo, oferecendo aos usuários a capacidade de pesquisar pacientes de forma eficiente pelos nomes. Isso pode melhorar significativamente a experiência do usuário e a eficiência na recuperação de dados.

8. Parabéns!

Você usou a biblioteca FHIR Engine para gerenciar recursos FHIR no seu app:

  • Usar a API Sync para sincronizar recursos FHIR com um servidor FHIR
  • Usar a API Data Access para criar, ler, atualizar e excluir recursos locais do FHIR
  • Usar a API Search para pesquisar recursos FHIR locais

O que aprendemos

  • Como configurar um servidor HAPI FHIR local
  • Como fazer upload de dados de teste para o servidor HAPI FHIR local
  • Como criar um app Android usando a biblioteca FHIR Engine
  • Como usar a API Sync, a API Data Access e a API Search na biblioteca do FHIR Engine

Próximas etapas

  • Confira a documentação da biblioteca do FHIR Engine
  • Conheça os recursos avançados da API Search
  • Aplicar a biblioteca FHIR Engine no seu próprio app Android

Saiba mais