Introdução

O aplicativo GuessTheWord no qual você trabalhou nos três tutoriais anteriores implementa o padrão de observador LiveData para observar os dados ViewModel. As vistas no controlador de IU observam o LiveData no ViewModel e atualizam os dados a serem exibidos.

Ao passar LiveData entre os componentes, às vezes você pode querer mapear ou transformar os dados. Seu código pode precisar realizar cálculos, exibir apenas um subconjunto dos dados ou alterar a renderização dos dados. Por exemplo, para a word LiveData, você pode criar uma transformação que retorna o número de letras da palavra em vez da própria palavra.

Você pode transformar o LiveData usando os métodos auxiliares na classe Transformations:

Neste tutorial, você adiciona um cronômetro de contagem regressiva no aplicativo. Você aprende a usar Transformations.map() no LiveData para transformar o tempo decorrido em um formato a ser exibido na tela.

O que você já deveria saber

O que aprenderá

O que fará

Nos tutoriais da Lição 5, você desenvolve o aplicativo GuessTheWord, começando com o código inicial. GuessTheWord é um jogo de estilo charadas para dois jogadores, onde os jogadores colaboram para atingir a pontuação mais alta possível.

O primeiro jogador olha para as palavras no aplicativo e representa cada uma delas, tomando cuidado para não mostrar a palavra ao segundo jogador. O segundo jogador tenta adivinhar a palavra.

Para jogar, o primeiro jogador abre o aplicativo no dispositivo e vê uma palavra, por exemplo "guitarra", conforme mostrado na imagem abaixo.

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

Neste tutorial, você aprimora o aplicativo GuessTheWord adicionando um cronômetro de contagem regressiva de um minuto que aparece acima da pontuação. O cronômetro termina o jogo quando a contagem regressiva atinge 0.

Você também pode usar uma transformação para formatar o objeto LiveData de tempo decorrido em um objeto de sequência de tempo LiveData. O LiveData transformado é a fonte de vinculação de dados para a vista de texto do temporizador.

Nesta tarefa, você localiza e executa seu código inicial para este tutorial. Você pode usar o aplicativo GuessTheWord que você construiu no tutorial anterior como seu código inicial ou pode baixar um aplicativo inicial.

  1. (Opcional) Se você não estiver usando o código do tutorial anterior, baixe o código inicial para este tutorial. Descompacte o código e abra o projeto no Android Studio.
  2. Execute o aplicativo e jogue o jogo.
  1. Observe que o botão Skip exibe a próxima palavra e diminui a pontuação em um, e o botão Got It mostra a próxima palavra e aumenta a pontuação em um. O botão End Game encerra o jogo.
  1. Percorra todas as palavras e observe que o aplicativo navega automaticamente para a tela de pontuação.

Nesta tarefa, você adiciona um cronômetro de contagem regressiva ao aplicativo. Em vez de o jogo terminar quando a lista de palavras estiver vazia, o jogo termina quando o cronômetro termina. O Android fornece uma classe de utilitário chamada CountDownTimer que você usa para implementar o cronômetro.

Adicione a lógica para o cronômetro no GameViewModel para que o cronômetro não seja destruído durante as mudanças de configuração. O fragmento contém o código para atualizar a vista do texto do cronômetro conforme o cronômetro avança.

Implemente as seguintes etapas na classe GameViewModel:

  1. Crie um objeto companion para manter as constantes do temporizador.
companion object {

   private const val DONE = 0L

   private const val ONE_SECOND = 1000L

   private const val COUNTDOWN_TIME = 60000L

}
  1. Para armazenar o tempo de contagem regressiva do cronômetro, adicione uma variável de membro MutableLiveData chamada _currentTime e uma propriedade de apoio, currentTime.
private val _currentTime = MutableLiveData<Long>()
val currentTime: LiveData<Long>
   get() = _currentTime
  1. Adicione uma variável de membro private chamada timer do tipo CountDownTimer. Você resolve o erro de inicialização na próxima etapa.
private val timer: CountDownTimer
  1. Dentro do bloco init, inicialize e inicie o cronômetro. Passe no tempo total, COUNTDOWN_TIME. Para o intervalo de tempo, use ONE_SECOND. Substitua os métodos de retorno de chamada onTick() e onFinish() e inicie o cronômetro.
timer = object : CountDownTimer(COUNTDOWN_TIME, ONE_SECOND) {

   override fun onTick(millisUntilFinished: Long) {
       
   }

   override fun onFinish() {
       
   }
}

timer.start()
  1. Implemente o método de retorno de chamada onTick(), que é chamado a cada intervalo ou a cada tick. Atualize o _currentTime, usando o parâmetro passado millisUntilFinished. O millisUntilFinished é a quantidade de tempo até o cronômetro terminar em milissegundos. Converta millisUntilFinished em segundos e atribua-o a _currentTime.
override fun onTick(millisUntilFinished: Long)
{
   _currentTime.value = millisUntilFinished/ONE_SECOND
}
  1. O método de retorno de chamada onFinish() é chamado quando o cronômetro termina. Implemente onFinish() para atualizar o _currentTime e acionar o evento de término do jogo.
override fun onFinish() {
   _currentTime.value = DONE
   onGameFinish()
}
  1. Atualize o método nextWord() para redefinir a lista de palavras quando a lista estiver vazia, em vez de terminar o jogo.
private fun nextWord() {
   if (wordList.isEmpty()) {
       resetList()
   } else {
   _word.value = wordList.removeAt(0)
}
  1. Dentro do método onCleared(), cancele o cronômetro para evitar vazamentos de memória. Você pode remover a instrução de log, pois ela não é mais necessária. O método onCleared() é chamado antes que o ViewModel seja destruído.
override fun onCleared() {
   super.onCleared()
   timer.cancel()
}
  1. Execute seu aplicativo e jogue o jogo. Aguarde 60 segundos e o jogo termina automaticamente. No entanto, o texto do temporizador não é exibido na tela. Você conserta isso a seguir.

O método Transformations.map() fornece uma maneira de realizar manipulações de dados na fonte LiveData e retornar um resultado LiveData objeto. Essas transformações não são calculadas a menos que um observador esteja observando o objeto LiveData retornado.

Este método usa a fonte LiveData e uma função como parâmetros. A função manipula a fonte LiveData.

Nesta tarefa, você formata o objeto LiveData de tempo decorrido em um novo objeto de string LiveData no formato "MM:SS". Você também exibe o tempo decorrido formatado na tela.

O arquivo de layout game_fragment.xml já inclui a vista do texto do cronômetro. Até agora, a vista de texto não possuia texto para exibir, portanto, o texto do cronômetro não estava visível.

  1. Na classe GameViewModel, após instanciar o currentTime, crie um objeto LiveData chamado currentTimeString. Este objeto é para a versão de string formatada de currentTime.
  2. Use Transformations.map() para definir currentTimeString. Passe o currentTime e uma função lambda para formatar a hora. Você pode implementar a função lambda usando o método de utilitário DateUtils.formatElapsedTime(), que leva um número long de milissegundos e o formata para "MM:SS"formato de string.
val currentTimeString = Transformations.map(currentTime) { time ->
   DateUtils.formatElapsedTime(time)
}
  1. No arquivo game_fragment.xml, na vista de texto do cronômetro, vincule o atributo text ao currentTimeString do gameViewModel.
<TextView
   android:id="@+id/timer_text"
   ...
   android:text="@{gameViewModel.currentTimeString}"
   ... />
  1. Execute seu aplicativo e jogue o jogo. O texto do cronômetro é atualizado uma vez por segundo. Observe que o jogo não termina quando você percorre todas as palavras. O jogo agora termina quando o cronômetro termina.

Parabéns! Você adicionou com sucesso um cronômetro ao aplicativo que encerra o jogo automaticamente. Você também aprendeu como usar Transformations.map() para converter um objeto LiveData em outro.

Projeto Android Studio: GuessTheWord

Desafio: Crie uma dica sobre a palavra e exiba a dica em uma vista de texto acima do cronômetro. Essa dica pode dizer quantos caracteres a palavra tem e pode revelar uma das letras em uma posição aleatória.

Dicas: Use Transformations.map() no objeto word LiveData atual. Adicione um TextView extra para exibir a dica da palavra.

  1. Na classe GameViewModel, adicione um val para transformar a palavra atual na dica.
val wordHint = Transformations.map(word) { word ->
   val randomPosition = (1..word.length).random()
   "Current word has " + word.length + " letters" +
           "\nThe letter at position " + randomPosition + " is " +
           word.get(randomPosition - 1).toUpperCase()
}
  1. Em game_fragment.xml, adicione uma nova vista de texto acima da vista de texto do cronômetro para exibir a dica. Vincule o atributo de texto ao wordHint que você adicionou acima.
android:text="@{gameViewModel.wordHint}"

Transformando LiveData

Exibindo o resultado de uma transformação em umTextView

val newResult = Transformations.map(someLiveData) { input ->
   
}
<data>
   <variable
       name="MyViewModel"
       type="com.example.android.something.MyViewModel" />
</data>
android:text="@{SomeViewModel.newResult}"

Formatando datas

Documentação para desenvolvimento em Android:

Esta seção lista as possíveis tarefas de casa para os alunos que estão trabalhando neste tutorial como parte de um curso ministrado por um instrutor.

Responda a essas perguntas

Pergunta 1

Em qual classe você deve adicionar a lógica de formatação de dados que usa o método Transformations.map() para converter LiveData em um valor ou formato diferente ?

Pergunta 2

O método Transformations.map() fornece uma maneira fácil de realizar manipulações de dados no LiveData e retorna __________.

Pergunta 3

Quais são os parâmetros para o método Transformations.map()?

Pergunta 4

A função lambda passada para o método Transformations.map() é executada em qual thread?

Comece a próxima lição: 06.1: Criando um banco de dados de salas

Para obter enlaces para outros tutoriais neste curso, consulte a página de destino dos tutoriais Fundamentos de Android em Kotlin.