0

Use ViewModel, Observers e LiveData para manipular a UI no Android com Kotlin

#Kotlin #Android
Francisco Rasia
Francisco Rasia

Nesse artigo vamos aprender alguns truques para manipular a UI à partir do ViewModel no Android com Kotlin. No primeiro tutorial, vamos usar coroutines e funções do tipo suspend lambda para mostrar e ocultar a barra de progresso durante a execução de uma chamada à API Web. No segundo tutorial, vamos capturar e exibir mensagens de erro usando um SnackBar.

Nos dois tutoriais vamos utilizar LiveData e Observers para manipular a UI e exibir mensagens para o usuário na Actitivity Principal.

Nossa demanda de hoje é muito breve - uma mera sidequest para render alguns XP e um pouco de tesouro. Todos prontos e prontas? Então vamos lá!


📯 O Projeto

Vamos continuar trabalhando no app Lib_ghi, o cliente de acesso à Studio Ghibli API. O código fonte está disponível no meu repositório do github - lembre-se de usar o commit 5d57442 e o branch exercicio-dispatcher-rec.

🌎 Studio Ghibli API v1.0.1: https://ghibliapi.herokuapp.com/#

💻 Repositório no github: https://github.com/chicorasia/bootcamp-libghi


🏹 Adicionando parâmetros ao ViewModel

Como eu prometi na chamada desse artigo, vamos usar o ViewModel para controlar elementos visuais da MainActivity. Para isso, vamos adicionar dois parâmetros ao nosso FilmListViewModel, usando a técnica de ter uma sombra do parâmetro:

private val _progressBar = MutableLiveData<Boolean>()
val progressBar: LiveData<Boolean>
  get() = _progressBar
  
private val _snackBar = MutableLiveData<String?>()
val snackbar: LiveData<String?>
  get() = _snackBar


Não se preocupe, isso já vai fazer sentido: vamos configurar observers na MainActivity, que irão exibir ou ocultar elementos visuais a partir das mudanças nos valores desses parâmetros😉. Repare que o tipo de dado do _snackBar precisa ser nulável.


🐲 Exibindo a ProgressBar

No vídeo e artigo anteriores eu mostrei como criar uma função suspend lambda. A função launchDataLoad() nossa classe ViewModel ficou assim:

private fun launchDataLoad(block: suspend () -> Unit){
    viewModelScope.launch {
      //alguma coisa antes
      block()
      //alguma coisa depois
   }
 }


Agora isso vai começar a fazer sentido. Veja que a estrutura da função é mais ou menos assim: execute alguma coisa -> execute o block() -> execute outra coisa. Pois essa estrutura tremendamente simples é útil quando queremos ter comportamentos padronizados, por exemplo: mostrar uma barra de progresso, executar uma tarefa e, finalmente, ocultar a barra de progresso.

Assim, podemos aprimorar nosso suspend lambda colocando o block() dentro de um bloco try-catch e modificando o valor da variável_progressBar. A última linha de código, dentro do bloco finally { }, vai assegurar que a ProgressBar fique invisível mesmo que ocorra um erro durante o carregamento. Vamos também colocar um delay de 1 segundo para o efeito visual ficar mais visível.

private fun launchDataLoad(block: suspend () -> Unit) {
  viewModelScope.launch {
    try {
      _progressBar.value = true
   delay(1000) //só para efeito visual...
      block()
   } catch (ex: Exception) {
      // faça um tratamento da exceção...
   } finally {
      _progressBar.value = false
   }
 }
}


Voltando à MainActivity, vamos eliminar ou comentar os métodos showProgressBar e hideProgressBar e adicionar um observer ao parâmetro mViewModel.progressBar; esse observer vai ter a responsabilidadde de alterar a visibilidade da ProgressBar:

mViewModel.progressBar.observe(this) {
  if(it) progressBar.visibility = View.VISIBLE
  else progressBar.visibility = View.GONE
}


Agora a visibilidade do ProgressBar fica vinculada às atividades do ViewModel - e veja que não prejudicamos o encapsulamento do código, ao contrário: aumentamos a separação de responsabilidades, pois o ViewModel somente indica se deve ser exibido o ProgressBar e a MainActivity não precisa saber os detalhes de implementação dos métodos de carregamento.


🏰 Exibindo um erro no SnackBar

Agora vamos melhorar o feedback ao usuário usando um SnackBar - aquelas mensagens curtinhas que pipocam na parte inferior da tela. Vamos usar uma solução muito parecida, modificando o método FilmListViewModel.getAllFilms() para que este atribua a mensagem de erro ao valor do _snackBar:

try {
  _filmList.postValue(FilmRepository().loadData())
} catch (error: FilmLoadError) {
  Log.e("lib_ghi", "getAllFilms: Ocorreu um erro do tipo FilmLoadError")
  _snackBar.value = error.message //atribui a mensagem do erro...
} 


Vamos também criar também o método onSnackBarShown()para resetar o texto depois de exibido:

fun onSnackBarShown() {
  _snackBar.value = null
}


E, na MainActivity, criamos um observer que mostra um SnackBar quando o valor do texto é modificado e não é nulo. Para isso, vamos fazer uma safe call usando a função .let { }. No final, invocamos o método onSnackBarShown para resetar o valor do parâmetro:

 mViewModel.snackbar.observe(this) { value: String? ->
      value?.let{
        Snackbar.make(binding.root, value, Snackbar.LENGTH_LONG).show()
        mViewModel.onSnackBarShown()
     }
   }
  


Ecco! Vamos rodar o app e ver os resultados: se tudo deu certo, o app vai mostrar uma barra de carregamento enquanto ocorre o acesso à API, e oculta a barra após recuperar os dados. Se testarmos o app no modo avião, a barra de progresso é exibida por alguns instantes e depois é exibido uma mensagem de erro para o usuário por meio de um SnackBar.


🗻 Conclusão

Nesse artigo nós aprendemos como o ViewModel, o LiveData e os Observers podem ser combinados para controlar elementos visuais de uma Activity; também vimos como usar uma função suspend lambda para simplificar chamadas repetitivas e reduzir a duplicação de código.

Além de melhorar a experiência do usuário com uma UI mais comunicativa (que exibe as mensagens de erro para o usuário), também melhoramos a coesão do código, reduzindo as responsabilidades da Activity Principal.


📚Para saber mais

💻 Repositório no github: https://github.com/chicorasia/bootcamp-libghi

🌎 Studio Ghibli API v1.0.1: https://ghibliapi.herokuapp.com/#

💻 Repositório no github: https://github.com/chicorasia/bootcamp-libghi

📃 Artigo: Avançando nas Coroutines e Dispatchers no Android com Kotlin


💊 Pílula de Kotlin: Controle a UI usando LiveData, ViewModel e Observers no Android: https://youtu.be/XSwGwYzZ1qo

💊 Pílula de Kotlin: Usando Coroutines e Dispatchers no Android: https://youtu.be/7-sNqUabVBo


📷

Noom Peerapong on Unsplash

1
24

Comentários (1)

0
Francisco Rasia

Francisco Rasia

20/04/2021 10:45

Olá!


Fiz algumas correções no artigo (tinha um erro em um exemplo de código) e adicionei o vídeo com o passo-a-passo:


https://youtu.be/XSwGwYzZ1qo


Abs e ótimos estudos



-Francisco

Arquiteto, urbanista, desenvolvedor Java & Android e criador em chefe na chicorialabs.com.br

Brasil