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

Este codelab faz parte do curso Conceitos básicos do Kotlin para Android. Você aproveitará mais o curso se fizer os codelabs em sequência. Todos os codelabs do curso estão listados na página de destino dos codelabs do curso Conceitos 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 do app sobrevivam às mudanças de configuração do dispositivo, como a rotação da tela e a disponibilidade do teclado. Você também adicionou observáveis LiveData, para que as visualizações sejam notificadas automaticamente quando os dados observados mudarem.

Neste codelab, você continuará trabalhando com o app GuessTheWord. Você vincula as 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 comunicaram indiretamente com o ViewModel, por meio dos fragmentos do app. Depois de integrar a vinculação de dados aos objetos ViewModel, você não precisará mais dos gerenciadores de clique nos fragmentos do app. Portanto, eles serão removidos.

Você também muda o app GuessTheWord para que use LiveData como a fonte de vinculação de dados para notificar a IU sobre mudanças nos dados sem usar os métodos de observador LiveData.

O que você já precisa saber

  • Saber 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 do LiveData.

O que você vai aprender

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

Atividades do laboratório

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

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

O primeiro jogador analisa as palavras no app e age uma por vez, garantindo que a palavra não seja exibida ao segundo jogador. O segundo participante tenta adivinhar a palavra.

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

O primeiro jogador age com a palavra, tendo cuidado para não dizer a palavra em si.

  • Quando o segundo jogador adivinhar a palavra corretamente, o primeiro pressione o botão Got It. Isso aumenta a contagem em um e exibe a próxima palavra.
  • Se o segundo jogador não conseguir adivinhar a palavra, o primeiro jogador pressionará o botão Skip, que diminui a contagem em um e pula para a próxima.
  • 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 o LiveData em objetos ViewModel. Isso automatiza a comunicação entre as visualizações no layout e os objetos ViewModel e permite que você simplifique seu código usando LiveData.

Créditos

Tela de jogo

Tela de pontuação

Nesta tarefa, você localizará e executará o código inicial para este codelab. Você pode usar o app GuessTheWord que criou no codelab anterior como seu código inicial ou fazer o download de um app inicial.

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

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

Arquitetura de app atual

No app, as visualizações são definidas no layout XML, e os dados delas são mantidos em objetos ViewModel. Entre cada visualização e o ViewModel correspondente é um controlador de IU, que funciona como um redirecionamento entre elas.

Exemplo:

  • O botão Got It é definido como uma visualização Button no arquivo de layout game_fragment.xml.
  • Quando o usuário toca no botão Ok, um listener de clique no fragmento GameFragment chama o listener de clique correspondente em GameViewModel.
  • A pontuação é atualizada no GameViewModel.

A visualização Button e o GameViewModel não se comunicam diretamente. Eles precisam do listener de cliques 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 IU como intermediários.

Os objetos ViewModel armazenam todos os dados da IU 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ê associará as classes GameViewModel e ScoreViewModel aos layouts XML correspondentes. Também é possível configurar vinculações de listener para processar eventos de clique.

Etapa 1: adicionar uma vinculação de dados ao GameViewModel

Nesta etapa, você 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 GameViewModel para a vinculação de dados.

    Para fazer isso, atribua viewModel à variável binding.gameViewModel que foi declarada na etapa anterior. Coloque esse código dentro de onCreateView(), depois que 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 processar 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 "detectado para" 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ê substituirá os listeners de clique no GameFragment por vinculações de listener no arquivo game_fragment.xml.

  1. No 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 do listener.
<Button
   android:id="@+id/skip_button"
   ...
   android:onClick="@{() -> gameViewModel.onSkip()}"
   ... />
  1. Da mesma forma, vincule o evento de clique de correct_button ao método onCorrect() no GameViewModel.
<Button
   android:id="@+id/correct_button"
   ...
   android:onClick="@{() -> gameViewModel.onCorrect()}"
   ... />
  1. Vincule o evento de clique de end_game_button ao método onGameFinish() no GameViewModel.
<Button
   android:id="@+id/end_game_button"
   ...
   android:onClick="@{() -> gameViewModel.onGameFinish()}"
   ... />
  1. No GameFragment, remova as instruções que definem os listeners de clique e as funções que eles chamam. Eles não são mais necessários.

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 a vinculação de dados ao ScoreViewModel

Nesta etapa, você 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. No score_fragment.xml, adicione o atributo onClick ao play_again_button. Defina uma vinculação do listener e chame o método onPlayAgain() no ScoreViewModel.
<Button
   android:id="@+id/play_again_button"
   ...
   android:onClick="@{() -> scoreViewModel.onPlayAgain()}"
   ... />
  1. No ScoreFragment, no método 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 recrie o projeto.

Código a ser removido:

binding.playAgainButton.setOnClickListener {  viewModel.onPlayAgain()  }
  1. Execute o app. Ele 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 com os gerenciadores de cliques no botão em ScoreFragment

Solução de problemas de 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 que são usadas para vinculação de dados. Um app pode ter erros que o Android Studio não detecta até que você tente compilar o app. Portanto, os avisos ou o código vermelho não serão exibidos enquanto você estiver escrevendo o código. No entanto, no tempo de compilação, você recebe erros criptográficos provenientes das classes intermediárias geradas.

Se você receber uma mensagem de erro criptográfica, faça o seguinte:

  1. Observe a mensagem com atenção na mensagem Build do Android Studio. Se um local terminar em databinding, ocorrerá um erro com a vinculação de dados.
  2. No arquivo XML de layout, verifique se há erros nos 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 de ortografia do nome da função onCorrect() no seguinte valor de atributo:

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

Observe também o erro ortográfico 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 e, em seguida, o compilador exiba 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, que é usado com objetos ViewModel. Agora que você adicionou a vinculação de dados aos objetos ViewModel, está tudo pronto para incorporar a LiveData.

Nesta tarefa, você mudará o app GuessTheWord para usar LiveData como a origem da vinculação de dados para notificar a IU sobre mudanças nos dados sem usar os métodos do observador LiveData.

Etapa 1: adicionar a palavra LiveData ao arquivo game_fragment.xml

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

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

Defina-o como o objeto LiveData, word do 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, é possível usar o objeto LiveData. O objeto LiveData exibe o valor atual do word. Se o valor de word for nulo, o objeto LiveData exibirá uma string vazia.

  1. Na GameFragment, no onCreateView(), depois de inicializar o gameViewModel, defina a atividade atual como a proprietária do ciclo de vida da variável binding. Isso define o escopo do objeto LiveData acima, permitindo que o objeto 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 word do LiveData.

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. Agora, a palavra atual é atualizada sem um método de observador no controlador de IU.

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

Nesta etapa, você vincula a score do LiveData à visualização de texto da pontuação no fragmento da pontuação.

  1. No score_fragment.xml, adicione o atributo android:text à visualização do texto da pontuação. Atribua scoreViewModel.score ao atributo text. Como o 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 a 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. A pontuação no fragmento é exibida corretamente, sem um observador.

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

No layout, você pode adicionar formatação de string com a vinculação de dados. Nesta tarefa, você formatará a palavra atual para adicionar aspas ao redor dela. Também é possível formatar a string de pontuação como prefixo da Pontuação atual, conforme a imagem a seguir.

  1. No string.xml, adicione as seguintes strings, que você usará para formatar as visualizações de texto word e score. %s e %d são os marcadores da palavra e da pontuação atuais.
<string name="quote_format">\"%s\"</string>
<string name="score_format">Current Score: %d</string>
  1. No 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. A palavra atual é transmitida 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 à word_text. No 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 %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, no 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 e depois jogue. A palavra atual e a pontuação são formatadas na tela do jogo.

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

Projeto do Android Studio: GuessTheWord

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

Vinculação de dados do ViewModel

  • Você pode associar um ViewModel a um layout usando a vinculação de dados.
  • Os objetos ViewModel armazenam os dados da IU. 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.

Como associar uma 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 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 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 IU por 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()}"

Como adicionar LiveData à vinculação de dados

  • Objetos LiveData podem ser usados como uma fonte de vinculação de dados para notificar automaticamente a IU sobre mudanças nos dados.
  • É possível 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 do observador nos controladores de IU.
android:text="@{gameViewModel.word}"
  • Para fazer com que a vinculação de dados LiveData funcione, defina a atividade atual (o controlador de IU) como a proprietária do ciclo de vida da variável binding no controlador de IU.
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 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 para os alunos que estão trabalhando neste codelab como parte de um curso ministrado por um instrutor. Cabe ao instrutor fazer o seguinte:

  • Se necessário, atribua o dever de casa.
  • Informe aos alunos como enviar o dever de casa.
  • Atribua nota aos trabalhos de casa.

Os professores podem usar essas sugestões o quanto quiserem, e eles devem se sentir à vontade para passar o dever de casa como achar adequado.

Se você estiver fazendo este codelab por conta própria, use essas atividades para testar seu conhecimento.

Responda a estas perguntas

Pergunta 1

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

  • 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 aplicativo 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 pela LiveData são alterados
  • Quando uma atividade é recriada por uma mudança de configuração
  • Quando ocorre um evento como onClick()
  • Quando a atividade entra em segundo plano

Inicie a próxima lição: 5.4: transformações do LiveData

Para ver links de outros codelabs neste curso, consulte a página de destino dos codelabs do curso Conceitos básicos do Kotlin para Android.