Conceitos básicos do Kotlin para Android 05.3: vinculação de dados com ViewModel e LiveData

Este codelab faz parte do curso Conceitos básicos do Kotlin para Android. Você vai aproveitar mais este curso se fizer os codelabs em sequência. Todos os codelabs do curso estão listados na página inicial dos codelabs de princípios básicos do Kotlin para Android.

Introdução

Nos codelabs anteriores desta lição, você melhorou o código do app GuessTheWord. Agora, o app usa objetos ViewModel, para que os dados sobrevivam a mudanças na configuração do dispositivo, como rotações de tela e mudanças na disponibilidade do teclado. Você também adicionou LiveData observáveis para que as visualizações sejam notificadas automaticamente quando os dados observados mudarem.

Neste codelab, você vai continuar trabalhando com o app GuessTheWord. Você vai vincular visualizações às classes ViewModel no app para que as visualizações no seu layout se comuniquem diretamente com os objetos ViewModel. Até agora, no seu app, as visualizações se comunicavam indiretamente com o ViewModel, por meio dos fragmentos do app. Depois de integrar a vinculação de dados aos objetos ViewModel, não é mais necessário ter manipuladores de cliques nos fragmentos do app. Portanto, remova-os.

Você também muda o app GuessTheWord para usar LiveData como a fonte de vinculação de dados para notificar a interface sobre mudanças nos dados, sem usar métodos observadores LiveData.

O que você já precisa saber

  • Como criar apps Android básicos em Kotlin.
  • Como os ciclos de vida de atividades e fragmentos funcionam.
  • Como usar objetos ViewModel no app.
  • Como armazenar dados usando LiveData em um ViewModel.
  • Como adicionar métodos do observador para observar as mudanças nos dados de LiveData.

O que você vai aprender

  • Como usar elementos da Data Binding Library.
  • Como integrar ViewModel com a vinculação de dados.
  • Como integrar LiveData com a vinculação de dados.
  • Como usar vinculações de listener para substituir os listeners de clique em um fragmento.
  • Como adicionar formatação de string a expressões de vinculação de dados.

Atividades deste laboratório

  • As visualizações nos layouts do GuessTheWord se comunicam indiretamente com objetos ViewModel, usando controladores de UI (fragmentos) para transmitir informações. Neste codelab, você vai vincular as visualizações do app a objetos ViewModel para que elas se comuniquem diretamente com os objetos ViewModel.
  • Você muda o app para usar LiveData como a fonte de vinculação de dados. Depois dessa mudança, os objetos LiveData notificam a UI sobre mudanças nos dados, e os métodos de observador LiveData não são mais necessários.

Nos codelabs da lição 5, você vai desenvolver o app GuessTheWord, começando com o código inicial. O GuessTheWord é um jogo de charadas para dois jogadores em que eles colaboram para conseguir a maior pontuação possível.

O primeiro jogador olha as palavras no app e representa cada uma delas, sem mostrar a palavra para o segundo jogador. O segundo jogador tenta adivinhar a palavra.

Para jogar, o primeiro jogador abre o app no dispositivo e vê uma palavra, por exemplo, "guitarra", como mostrado na captura de tela abaixo.

O primeiro jogador representa a palavra, tomando cuidado para não dizer a palavra em si.

  • Quando o segundo jogador acerta a palavra, o primeiro pressiona o botão Entendi, que aumenta a contagem em um e mostra a próxima palavra.
  • Se o segundo jogador não conseguir adivinhar a palavra, o primeiro jogador pressiona o botão Pular, que diminui a contagem em um e pula para a próxima palavra.
  • Para encerrar o jogo, pressione o botão Encerrar jogo. Essa funcionalidade não está no código inicial do primeiro codelab da série.

Neste codelab, você vai melhorar o app GuessTheWord integrando a vinculação de dados com LiveData em objetos ViewModel. Isso automatiza a comunicação entre as visualizações no layout e os objetos ViewModel, além de simplificar o código usando LiveData.

Créditos

Tela do jogo

Tela de pontuação

Nesta tarefa, você vai localizar e executar o código inicial deste codelab. Você pode usar o app GuessTheWord criado no codelab anterior como código inicial ou baixar um app inicial.

  1. (Opcional) Se você não estiver usando o código do codelab anterior, faça o download do código inicial para este codelab. Descompacte o código e abra o projeto no Android Studio.
  2. Execute o app e jogue.
  3. O botão Entendi mostra a próxima palavra e aumenta a pontuação em um, enquanto o botão Pular mostra a próxima palavra e diminui a pontuação em um. O botão Encerrar jogo encerra a partida.
  4. Percorra todas as palavras e observe que o app navega automaticamente até a tela de pontuação.

Em um codelab anterior, você usou a vinculação de dados como uma maneira com segurança de tipos de acessar as visualizações no app GuessTheWord. Mas o verdadeiro poder da vinculação de dados está em fazer o que o nome sugere: vincular dados diretamente aos objetos de visualização no seu app.

Arquitetura atual do app

No seu app, as visualizações são definidas no layout XML, e os dados dessas visualizações são mantidos em objetos ViewModel. Entre cada visualização e o ViewModel correspondente, há um controlador de interface, que atua como um retransmissor entre eles.

Exemplo:

  • O botão Entendi é definido como uma visualização Button no arquivo de layout game_fragment.xml.
  • Quando o usuário toca no botão Entendi, um listener de clique no fragmento GameFragment chama o listener de clique correspondente em GameViewModel.
  • O placar é atualizado no GameViewModel.

A visualização Button e o GameViewModel não se comunicam diretamente. Eles precisam do listener de clique que está no GameFragment.

ViewModel transmitido para a vinculação de dados

Seria mais simples se as visualizações no layout se comunicassem diretamente com os dados nos objetos ViewModel, sem depender de controladores de interface como intermediários.

Os objetos ViewModel contêm todos os dados da interface do usuário no app GuessTheWord. Ao transmitir objetos ViewModel para a vinculação de dados, você pode automatizar parte da comunicação entre as visualizações e os objetos ViewModel.

Nesta tarefa, você vai associar as classes GameViewModel e ScoreViewModel aos layouts XML correspondentes. Você também configurou vinculações de listener para processar eventos de clique.

Etapa 1: adicionar vinculação de dados para o GameViewModel

Nesta etapa, você vai associar GameViewModel ao arquivo de layout correspondente, game_fragment.xml.

  1. No arquivo game_fragment.xml, adicione uma variável de vinculação de dados do tipo GameViewModel. Se houver erros no Android Studio, limpe e recrie o projeto.
<layout ...>

   <data>

       <variable
           name="gameViewModel"
           type="com.example.android.guesstheword.screens.game.GameViewModel" />
   </data>
  
   <androidx.constraintlayout...
  1. No arquivo GameFragment, transmita o GameViewModel para a vinculação de dados.

    Para fazer isso, atribua viewModel à variável binding.gameViewModel, que você declarou na etapa anterior. Coloque este código dentro de onCreateView(), depois que o viewModel for inicializado. Se houver erros no Android Studio, limpe e recrie o projeto.
// Set the viewmodel for databinding - this allows the bound layout access 
// to all the data in the ViewModel
binding.gameViewModel = viewModel

Etapa 2: usar vinculações de listener para processamento de eventos

As vinculações de listener são expressões de vinculação executadas quando eventos como onClick(), onZoomIn() ou onZoomOut() são acionados. As vinculações de listener são programadas como expressões lambda.

A vinculação de dados cria um listener e o define na visualização. Quando o evento monitorado acontece, o listener avalia a expressão lambda. As vinculações de listener funcionam com o Plug-in do Android para Gradle versão 2.0 ou mais recente. Para saber mais, leia Layouts e expressões de vinculação.

Nesta etapa, você vai substituir os listeners de clique no GameFragment por vinculações de listener no arquivo game_fragment.xml.

  1. Em game_fragment.xml, adicione o atributo onClick ao skip_button. Defina uma expressão de vinculação e chame o método onSkip() no GameViewModel. Essa expressão de vinculação é chamada de vinculação de listener.
<Button
   android:id="@+id/skip_button"
   ...
   android:onClick="@{() -> gameViewModel.onSkip()}"
   ... />
  1. Da mesma forma, vincule o evento de clique do correct_button ao método onCorrect() no GameViewModel.
<Button
   android:id="@+id/correct_button"
   ...
   android:onClick="@{() -> gameViewModel.onCorrect()}"
   ... />
  1. Vincule o evento de clique do end_game_button ao método onGameFinish() na GameViewModel.
<Button
   android:id="@+id/end_game_button"
   ...
   android:onClick="@{() -> gameViewModel.onGameFinish()}"
   ... />
  1. Em GameFragment, remova as instruções que definem os listeners de clique e as funções que eles chamam. Você não precisa mais deles.

Código a ser removido:

binding.correctButton.setOnClickListener { onCorrect() }
binding.skipButton.setOnClickListener { onSkip() }
binding.endGameButton.setOnClickListener { onEndGame() }

/** Methods for buttons presses **/
private fun onSkip() {
   viewModel.onSkip()
}
private fun onCorrect() {
   viewModel.onCorrect()
}
private fun onEndGame() {
   gameFinished()
}

Etapa 3: adicionar vinculação de dados para o ScoreViewModel

Nesta etapa, você vai associar ScoreViewModel ao arquivo de layout correspondente, score_fragment.xml.

  1. No arquivo score_fragment.xml, adicione uma variável de vinculação do tipo ScoreViewModel. Esta etapa é semelhante ao que você fez na GameViewModel acima.
<layout ...>
   <data>
       <variable
           name="scoreViewModel"
           type="com.example.android.guesstheword.screens.score.ScoreViewModel" />
   </data>
   <androidx.constraintlayout.widget.ConstraintLayout
  1. Em score_fragment.xml, adicione o atributo onClick ao play_again_button. Defina uma vinculação de listener e chame o método onPlayAgain() no ScoreViewModel.
<Button
   android:id="@+id/play_again_button"
   ...
   android:onClick="@{() -> scoreViewModel.onPlayAgain()}"
   ... />
  1. Em ScoreFragment, dentro de onCreateView(), inicialize o viewModel. Em seguida, inicialize a variável de vinculação binding.scoreViewModel.
viewModel = ...
binding.scoreViewModel = viewModel
  1. Em ScoreFragment, remova o código que define o listener de clique para o playAgainButton. Se o Android Studio mostrar um erro, limpe e reconstrua o projeto.

Código a ser removido:

binding.playAgainButton.setOnClickListener {  viewModel.onPlayAgain()  }
  1. Execute o app. Ele vai funcionar como antes, mas agora as visualizações de botão se comunicam diretamente com os objetos ViewModel. As visualizações não se comunicam mais pelos gerenciadores de cliques de botão em ScoreFragment.

Como resolver mensagens de erro de vinculação de dados

Quando um app usa a vinculação de dados, o processo de compilação gera classes intermediárias usadas para a vinculação de dados. Um app pode ter erros que o Android Studio não detecta até que você tente compilá-lo. Por isso, não aparecem avisos ou código vermelho enquanto você está escrevendo o código. Mas, no momento da compilação, você recebe erros enigmáticos das classes intermediárias geradas.

Se você receber uma mensagem de erro enigmática:

  1. Leia com atenção a mensagem no painel Build do Android Studio. Se você encontrar um local que termina em databinding, há um erro com a vinculação de dados.
  2. No arquivo XML de layout, verifique se há erros em atributos onClick que usam vinculação de dados. Procure a função que a expressão lambda chama e verifique se ela existe.
  3. Na seção <data> do XML, verifique a ortografia da variável de vinculação de dados.

Por exemplo, observe o erro ortográfico no nome da função onCorrect() no seguinte valor de atributo:

android:onClick="@{() -> gameViewModel.onCorrectx()}"

Observe também o erro de ortografia de gameViewModel na seção <data> do arquivo XML:

<data>
   <variable
       name="gameViewModelx"
       type="com.example.android.guesstheword.screens.game.GameViewModel" />
</data>

O Android Studio não detecta erros como esses até que você compile o app. Depois, o compilador mostra uma mensagem de erro como esta:

error: cannot find symbol
import com.example.android.guesstheword.databinding.GameFragmentBindingImpl"

symbol:   class GameFragmentBindingImpl
location: package com.example.android.guesstheword.databinding

A vinculação de dados funciona bem com LiveData usado com objetos ViewModel. Agora que você adicionou a vinculação de dados aos objetos ViewModel, já pode incorporar LiveData.

Nesta tarefa, você vai mudar o app GuessTheWord para usar LiveData como a fonte de vinculação de dados e notificar a interface sobre mudanças nos dados sem usar os métodos de observador LiveData.

Etapa 1: adicionar o LiveData de palavras ao arquivo game_fragment.xml

Nesta etapa, você vai vincular a visualização de texto da palavra atual diretamente ao objeto LiveData no ViewModel.

  1. Em game_fragment.xml, adicione o atributo android:text à visualização de texto word_text.

Defina como o objeto LiveData, word de GameViewModel, usando a variável de vinculação gameViewModel.

<TextView
   android:id="@+id/word_text"
   ...
   android:text="@{gameViewModel.word}"
   ... />

Não é necessário usar word.value. Em vez disso, use o objeto LiveData real. O objeto LiveData mostra o valor atual do word. Se o valor de word for nulo, o objeto LiveData vai mostrar uma string vazia.

  1. No GameFragment, em onCreateView(), depois de inicializar o gameViewModel, defina a atividade atual como proprietária do ciclo de vida da variável binding. Isso define o escopo do objeto LiveData acima, permitindo que ele atualize automaticamente as visualizações no layout, game_fragment.xml.
binding.gameViewModel = ...
// Specify the current activity as the lifecycle owner of the binding.
// This is used so that the binding can observe LiveData updates
binding.lifecycleOwner = this
  1. Em GameFragment, remova o observador do LiveData word.

Código a ser removido:

/** Setting up LiveData observation relationship **/
viewModel.word.observe(this, Observer { newWord ->
   binding.wordText.text = newWord
})
  1. Execute o app e jogue uma partida. Agora, a palavra atual está sendo atualizada sem um método observador no controlador da interface.

Etapa 2: adicionar o LiveData de pontuação ao arquivo score_fragment.xml

Nesta etapa, você vai vincular o LiveData score à visualização de texto da pontuação no fragmento de pontuação.

  1. Em score_fragment.xml, adicione o atributo android:text à visualização de texto da pontuação. Atribua scoreViewModel.score ao atributo text. Como score é um número inteiro, converta-o em uma string usando String.valueOf().
<TextView
   android:id="@+id/score_text"
   ...
   android:text="@{String.valueOf(scoreViewModel.score)}"
   ... />
  1. Em ScoreFragment, depois de inicializar o scoreViewModel, defina a atividade atual como proprietária do ciclo de vida da variável binding.
binding.scoreViewModel = ...
// Specify the current activity as the lifecycle owner of the binding.
// This is used so that the binding can observe LiveData updates
binding.lifecycleOwner = this
  1. Em ScoreFragment, remova o observador do objeto score.

Código a ser removido:

// Add observer for score
viewModel.score.observe(this, Observer { newScore ->
   binding.scoreText.text = newScore.toString()
})
  1. Execute o app e jogue uma partida. Observe que a pontuação no fragmento de pontuação é mostrada corretamente, sem um observador no fragmento de pontuação.

Etapa 3: adicionar formatação de strings com vinculação de dados

No layout, é possível adicionar formatação de string e vinculação de dados. Nesta tarefa, você vai formatar a palavra atual para adicionar aspas ao redor dela. Você também formata a string de pontuação para adicionar o prefixo Pontuação atual, como mostrado na imagem a seguir.

  1. Em string.xml, adicione as seguintes strings, que serão usadas para formatar as visualizações de texto word e score. Os %s e %d são os marcadores de posição para a palavra e a pontuação atuais.
<string name="quote_format">\"%s\"</string>
<string name="score_format">Current Score: %d</string>
  1. No arquivo game_fragment.xml, atualize o atributo text da visualização de texto word_text para usar o recurso de string quote_format. Transmita gameViewModel.word. Isso transmite a palavra atual como um argumento para a string de formatação.
<TextView
   android:id="@+id/word_text"
   ...
   android:text="@{@string/quote_format(gameViewModel.word)}"
   ... />
  1. Formate a visualização de texto score de maneira semelhante ao word_text. No arquivo game_fragment.xml, adicione o atributo text à visualização de texto score_text. Use o recurso de string score_format, que usa um argumento numérico, representado pelo marcador de posição %d. Transmita o objeto LiveData, score, como um argumento para essa string de formatação.
<TextView
   android:id="@+id/score_text"
   ...
   android:text="@{@string/score_format(gameViewModel.score)}"
   ... />
  1. Na classe GameFragment, dentro do método onCreateView(), remova o código do observador score.

Código a ser removido:

viewModel.score.observe(this, Observer { newScore ->
   binding.scoreText.text = newScore.toString()
})
  1. Limpe, recrie e execute o app. Depois, jogue uma partida. A palavra atual e a pontuação são formatadas na tela do jogo.

Parabéns! Você integrou LiveData e ViewModel à vinculação de dados no seu app. Isso permite que as visualizações no seu layout se comuniquem diretamente com o ViewModel, sem usar processadores de cliques no fragmento. Você também usou objetos LiveData como a fonte de vinculação de dados para notificar automaticamente a interface sobre mudanças nos dados, sem os métodos de observador LiveData.

Projeto do Android Studio: GuessTheWord (link em inglês)

  • A Data Binding Library funciona perfeitamente com os componentes da arquitetura do Android, como ViewModel e LiveData.
  • Os layouts do seu app podem se vincular aos dados dos componentes de arquitetura, que já ajudam a gerenciar o ciclo de vida do controlador da interface e a notificar mudanças nos dados.

Vinculação de dados do ViewModel

  • É possível associar um ViewModel a um layout usando a vinculação de dados.
  • Os objetos ViewModel contêm os dados da interface. Ao transmitir objetos ViewModel para a vinculação de dados, é possível automatizar parte da comunicação entre as visualizações e os objetos ViewModel.

Como associar um ViewModel a um layout:

  • No arquivo de layout, adicione uma variável de vinculação de dados do tipo ViewModel.
   <data>

       <variable
           name="gameViewModel"
           type="com.example.android.guesstheword.screens.game.GameViewModel" />
   </data>
  • No arquivo GameFragment, transmita o GameViewModel para a vinculação de dados.
binding.gameViewModel = viewModel

Vinculações de listener

  • As vinculações de listener são expressões de vinculação no layout que são executadas quando eventos de clique, como onClick(), são acionados.
  • As vinculações de listener são programadas como expressões lambda.
  • Usando vinculações de listener, você substitui os listeners de clique nos controladores da interface com vinculações de listener no arquivo de layout.
  • A vinculação de dados cria um listener e o define na visualização.
 android:onClick="@{() -> gameViewModel.onSkip()}"

Adicionar LiveData à vinculação de dados

  • Os objetos LiveData podem ser usados como uma fonte de vinculação de dados para notificar automaticamente a interface sobre mudanças nos dados.
  • Você pode vincular a visualização diretamente ao objeto LiveData no ViewModel. Quando o LiveData no ViewModel muda, as visualizações no layout podem ser atualizadas automaticamente, sem os métodos de observador nos controladores de UI.
android:text="@{gameViewModel.word}"
  • Para que a vinculação de dados LiveData funcione, defina a atividade atual (o controlador de UI) como o proprietário do ciclo de vida da variável binding no controlador de UI.
binding.lifecycleOwner = this

Formatação de strings com vinculação de dados

  • Com a vinculação de dados, é possível formatar um recurso de string com marcadores de posição como %s para strings e %d para números inteiros.
  • Para atualizar o atributo text da visualização, transmita o objeto LiveData como um argumento para a string de formatação.
 android:text="@{@string/quote_format(gameViewModel.word)}"

Curso da Udacity:

Documentação do desenvolvedor Android:

Esta seção lista as possíveis atividades de dever de casa para os alunos que estão fazendo este codelab como parte de um curso ministrado por um professor. Cabe ao professor fazer o seguinte:

  • Atribuir o dever de casa, se necessário.
  • Informar aos alunos como enviar deveres de casa.
  • Atribuir nota aos deveres de casa.

Os professores podem usar essas sugestões o quanto quiserem, podendo passar os exercícios que acharem mais apropriados como dever de casa.

Se você estiver seguindo este codelab por conta própria, sinta-se à vontade para usar esses deveres de casa para testar seu conhecimento.

Responda estas perguntas

Pergunta 1

Qual das seguintes afirmações não é verdadeira sobre vinculações de listener?

  • As vinculações de listener são expressões de vinculação executadas quando um evento acontece.
  • As vinculações de listener funcionam com todas as versões do Plug-in do Android para Gradle.
  • As vinculações de listener são programadas como expressões lambda.
  • As vinculações de listener são semelhantes às referências de método, mas permitem que você execute expressões de vinculação de dados arbitrárias.

Pergunta 2

Suponha que seu app inclua este recurso de string:
<string name="generic_name">Hello %s</string>

Qual das seguintes opções é a sintaxe correta para formatar a string usando a expressão de vinculação de dados?

  • android:text= "@{@string/generic_name(user.name)}"
  • android:text= "@{string/generic_name(user.name)}"
  • android:text= "@{@generic_name(user.name)}"
  • android:text= "@{@string/generic_name,user.name}"

Pergunta 3

Quando uma expressão de vinculação de listener é avaliada e executada?

  • Quando os dados retidos pelo LiveData são alterados
  • Quando uma atividade é recriada por uma mudança de configuração
  • Quando um evento como onClick() ocorre
  • Quando a atividade entra em segundo plano

Comece a próxima lição: 5.4: transformações de LiveData

Para acessar links de outros codelabs neste curso, consulte a página inicial dos codelabs de conceitos básicos do Kotlin para Android.