No tutorial anterior, você usou um ViewModel
no aplicativo GuessTheWord para permitir que os dados do aplicativo sobrevivam às alterações de configuração do dispositivo. Neste tutorial, você aprende como integrar LiveData
com os dados nas classes ViewModel
. LiveData
, que é um dos Componentes de arquitetura do Android, permite construir objetos de dados que notificam as vistas quando o banco de dados subjacente muda.
Para usar a classe LiveData
, você configura "observadores" (por exemplo, atividades ou fragmentos) que observam mudanças nos dados do aplicativo. LiveData
reconhece o ciclo de vida, portanto, atualiza apenas observadores de componentes de aplicativos que estão em um estado de ciclo de vida ativo.
ViewModel
em seu aplicativo.ViewModel
usando a interface ViewModelProvider.Factory
.
LiveData
úteis.LiveData
aos dados armazenados em um ViewModel
.MutableLiveData
.LiveData
.LiveData
usando uma propriedade de apoio.ViewModel
correspondente.LiveData
para a palavra e a pontuação no aplicativo GuessTheWord.LiveData
para adicionar um evento de jogo terminado.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 evento para encerrar o jogo quando o usuário percorre todas as palavras no aplicativo. Você também adiciona um botão Play Again no fragmento de pontuação, para que o usuário possa jogar o jogo novamente.
Tela principal |
Tela do jogo |
Tela de pontuação |
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.
LiveData
é uma classe de recipiente de dados observável que reconhece o ciclo de vida. Por exemplo, você pode envolver um LiveData
em torno da pontuação atual no aplicativo GuessTheWord. Neste tutorial, você aprende sobre várias características do LiveData
:
LiveData
é observável, o que significa que um observador é notificado quando os dados mantidos pelo objeto LiveData
são alterados.LiveData
contém dados; LiveData
é um embrulho que pode ser usado com qualquer dadoLiveData
reconhece o ciclo de vida. Quando você anexa um observador ao LiveData
, o observador é associado a um LifecycleOwner
(geralmente uma atividade ou fragmento). O LiveData atualiza apenas observadores que estão em um estado de ciclo de vida ativo, como STARTED
ou RESUMED
. Você pode ler mais sobre LiveData
e observação aqui.Nesta tarefa, você aprenderá como envolver qualquer tipo de dados em objetos LiveData
, convertendo a pontuação atual e os dados da palavra atual no GameViewModel
em LiveData
. Em uma tarefa posterior, você adiciona um observador a esses objetos LiveData
e aprende como observar o LiveData
.
screens/game
, abra o arquivo GameViewModel
.score
e word
para MutableLiveData
.MutableLiveData
é um LiveData
cujo valor pode ser alterado. MutableLiveData
é uma classe genérica, então você precisa especificar o tipo de dados que ela contém.val word = MutableLiveData<String>()
val score = MutableLiveData<Int>()
GameViewModel
, dentro do bloco init
, inicialize score
e word
. Para mudar o valor de uma variável LiveData
, você usa o método setValue()
na variável. No Kotlin, você pode chamar setValue()
usando a propriedade value
.init {
word.value = ""
score.value = 0
...
}
As variáveis score
e word
agora são do tipo LiveData
. Nesta etapa, você altera as referências a essas variáveis, usando a propriedade value
.
GameViewModel
, no método onSkip()
, altere score
para score.value
. Observe o erro sobre score
possivelmente ser null
. Você corrige este erro a seguir.null
a score.value
em onSkip()
. Em seguida, chame a função minus()
em score
, que realiza a subtração com null
-safety.fun onSkip() {
score.value = (score.value)?.minus(1)
nextWord()
}
onCorrect()
da mesma maneira: Adicione um cheque null
à variável score
e use o plus()
função.fun onCorrect() {
score.value = (score.value)?.plus(1)
nextWord()
}
GameViewModel
, dentro do método nextWord()
, altere a referência de word
para word
.
value
.private fun nextWord() {
if (!wordList.isEmpty()) {
//Select and remove a word from the list
word.value = wordList.removeAt(0)
}
}
GameFragment
, dentro do método updateWordText()
, altere a referência para viewModel
.word
para viewModel
.
word
.
value
.
private fun updateWordText() {
binding.wordText.text = viewModel.word.value
}
GameFragment
, dentro do método updateScoreText()
, altere a referência para o viewModel
.score
para viewModel
.
score
.
value
.
private fun updateScoreText() {
binding.scoreText.text = viewModel.score.value.toString()
}
GameFragment
, dentro do método gameFinished()
, altere a referência para viewModel
.score
para viewModel
.
score
.
value
. Adicione a verificação de segurança null
necessária.private fun gameFinished() {
Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
val action = GameFragmentDirections.actionGameToScore()
action.score = viewModel.score.value?:0
NavHostFragment.findNavController(this).navigate(action)
}
Esta tarefa está intimamente relacionada à tarefa anterior, onde você converteu a pontuação e os dados de palavras em objetos LiveData
. Nesta tarefa, você anexa objetos Observer
a esses objetos LiveData
. Você usará a vista de fragmento (viewLifecycleOwner
) como o LifecycleOwner
.
GameFragment
, dentro do método onCreateView()
, anexe um objeto Observer
ao objeto LiveData
para a pontuação atual, viewModel.score
. Use o método observe()
e coloque o código após a inicialização do viewModel
. Use uma expressão lambda para simplificar o código. (Uma expressão lambda é uma função anônima que não é declarada, mas é passada imediatamente como uma expressão).viewModel.score.observe(viewLifecycleOwner, Observer { newScore ->
})
Resolva a referência ao Observer
. Para fazer isso, clique em Observer
, pressione Alt+Enter
(Option+Enter
em um Mac) e importe androidx.lifecycle.Observer
.
LiveData
observado são alterados. Dentro do observador, atualize a partitura TextView
com a nova partitura.viewModel.score.observe(viewLifecycleOwner, Observer { newScore ->
binding.scoreText.text = newScore.toString()
})
Observer
ao objeto LiveData
da palavra atual. Faça da mesma forma que anexou um objeto Observer
à partitura atual.viewModel.word.observe(viewLifecycleOwner, Observer { newWord ->
binding.wordText.text = newWord
})
Quando o valor de score
ou a word
muda, a score
ou word
exibida na tela agora é atualizada automaticamente.
GameFragment
, exclua os métodos updateWordText()
e updateScoreText()
, e todas as referências a eles. Você não precisa mais deles, pois as vistas de texto são atualizadas pelos métodos do observador LiveData
. LiveData
e LiveData
.Encapsulamento é uma forma de restringir o acesso direto a alguns dos campos de um objeto. Ao encapsular um objeto, você expõe um conjunto de métodos públicos que modificam os campos internos privados. Usando o encapsulamento, você controla como outras classes manipulam esses campos internos.
Em seu código atual, qualquer classe externa pode modificar as variáveis score
e word
usando a propriedade value
, por exemplo, usando viewModel.score.value
. Pode não importar no aplicativo que você está desenvolvendo neste tutorial, mas em um aplicativo de produção, você deseja controlar os dados nos objetos ViewModel
.
Apenas o ViewModel
deve editar os dados em seu aplicativo. Mas os controladores de IU precisam ler os dados, então os campos de dados não podem ser privados. Para encapsular os dados do seu aplicativo, você usa os objetos MutableLiveData
e LiveData
.
MutableLiveData
vs. LiveData
:
MutableLiveData
podem ser alterados, como o nome indica. Dentro do ViewModel
, os dados devem ser editáveis, então ele usa MutableLiveData
. LiveData
podem ser lidos, mas não alterados. Fora do ViewModel
, os dados devem ser legíveis, mas não editáveis, portanto, os dados devem ser expostos como LiveData
.Para executar essa estratégia, você usa uma propriedade de apoio do Kotlin. Uma propriedade backing permite que você retorne algo de um getter diferente do objeto exato. Nesta tarefa, você implementa uma propriedade de apoio para os objetos score
e word
no aplicativo GuessTheWord.
GameViewModel
, torne o objeto score
atual private
.score
para _score
. A propriedade _score
agora é a versão mutável da pontuação do jogo, para ser usada internamente.LiveData
, chamada score
. private val _score = MutableLiveData<Int>()
val score: LiveData<Int>
GameFragment
, o score
é uma referência do LiveData
e score
não pode mais acessar seu setter. Para saber mais sobre getters e setters em Kotlin, consulte Getters e Setters.get()
método para o objeto score
em GameViewModel
e retorna a propriedade de apoio, _score
. val score: LiveData<Int>
get() = _score
GameViewModel
, altere as referências de score
para sua versão mutável interna, _score
.init {
...
_score.value = 0
...
}
...
fun onSkip() {
_score.value = (score.value)?.minus(1)
...
}
fun onCorrect() {
_score.value = (score.value)?.plus(1)
...
}
word
para _word
e adicione uma propriedade de apoio para ele, como você fez para o objeto score
.private val _word = MutableLiveData<String>()
val word: LiveData<String>
get() = _word
...
init {
_word.value = ""
...
}
...
private fun nextWord() {
if (!wordList.isEmpty()) {
_word.value = wordList.removeAt(0)
}
}
Ótimo trabalho, você encapsulou os objetos LiveData
word
e score
.
Seu aplicativo atual navega para a tela de pontuação quando o usuário toca no botão End Game. Você também deseja que o aplicativo navegue até a tela de pontuação quando os jogadores tiverem passado por todas as palavras. Depois que os jogadores terminarem com a última palavra, você deseja que o jogo termine automaticamente para que o usuário não precise tocar no botão.
Para implementar esta funcionalidade, você precisa que um evento seja disparado e comunicado ao fragmento do ViewModel
quando todas as palavras forem mostradas. Para fazer isso, você usa o padrão de observador LiveData
para modelar um evento de jogo terminado.
O padrão do observador é um padrão de projeto de software. Ele especifica a comunicação entre objetos: Um observável (o "sujeito" da observação) e observadores. Um observável é um objeto que notifica os observadores sobre as mudanças em seu estado.
No caso de LiveData
neste aplicativo, o observável (sujeito) é o objeto LiveData
e os observadores são os métodos nos controladores de IU, como fragmentos. Uma mudança de estado ocorre sempre que os dados agrupados em LiveData
são alterados. As classes LiveData
são cruciais na comunicação do ViewModel
para o fragmento.
Nesta tarefa, você usa o padrão de observador LiveData
para modelar um evento de jogo terminado.
GameViewModel
, crie um objeto Boolean
MutableLiveData
chamado _eventGameFinish
. Este objeto conterá o evento de finalização do jogo. _eventGameFinish
, crie e inicialize uma propriedade de apoio chamada eventGameFinish
.private val _eventGameFinish = MutableLiveData<Boolean>()
val eventGameFinish: LiveData<Boolean>
get() = _eventGameFinish
GameViewModel
, adicione um método onGameFinish()
. No método, defina o evento de finalização do jogo, eventGameFinish
, como true
.fun onGameFinish() {
_eventGameFinish.value = true
}
GameViewModel
, dentro do método nextWord()
, encerre o jogo se a lista de palavras estiver vazia. private fun nextWord() {
if (wordList.isEmpty()) {
onGameFinish()
} else {
//Select and remove a _word from the list
_word.value = wordList.removeAt(0)
}
}
GameFragment
, dentro de onCreateView()
, após inicializar o viewModel
, anexe um observador a eventGameFinish
. Use o método observe()
. Dentro da função lambda, chame o método gameFinished()
.viewModel.eventGameFinish.observe(viewLifecycleOwner, Observer<Boolean> { hasFinished ->
if (hasFinished) gameFinished()
})
eventGameFinish
é definido, o método de observador associado no fragmento do jogo é chamado e o aplicativo navega para o fragmento de tela.
GameFragment
, comente o código de navegação no método gameFinished()
. Certifique-se de manter a mensagem Toast
no método.private fun gameFinished() {
Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
// val action = GameFragmentDirections.actionGameToScore()
// action.score = viewModel.score.value?:0
// NavHostFragment.findNavController(this).navigate(action)
}
Agora gire o dispositivo ou emulador. O toast é exibido novamente! Gire o dispositivo mais algumas vezes e provavelmente você verá a toast todas as vezes. Isso é um erro, pois a toast somente deve ser exibida uma vez, quando o jogo terminar. A toast não deve ser exibida sempre que o fragmento for recriado. Você resolverá esse problema na próxima tarefa.
Normalmente, o LiveData
entrega atualizações aos observadores apenas quando os dados são alterados. Uma exceção a esse comportamento é que os observadores também recebem atualizações quando o observador muda de um estado inativo para um estado ativo.
É por isso que o toast ao final do jogo é acionado repetidamente em seu aplicativo. Quando o fragmento do jogo é recriado após uma rotação da tela, ele passa de um estado inativo para um estado ativo. O observador no fragmento é reconectado ao ViewModel
existente e recebe os dados atuais. O método gameFinished()
é disparado novamente e a notificação do sistema é exibida.
Nesta tarefa, você corrige este problema e exibe a notificação do sistema apenas uma vez, redefinindo o sinalizador eventGameFinish
no GameViewModel
.
GameViewModel
, adicione um método onGameFinishComplete()
para redefinir o evento de finalização do jogo, _eventGameFinish
.fun onGameFinishComplete() {
_eventGameFinish.value = false
}
GameFragment
, no final de gameFinished()
, chame onGameFinishComplete()
no viewModel
objeto. (Deixe o código de navegação em gameFinished()
comentado por enquanto).private fun gameFinished() {
...
viewModel.onGameFinishComplete()
}
GameFragment
, dentro do método gameFinished()
, descomente o código de navegação.Control+/
(Command+/
em um Mac).private fun gameFinished() {
Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
val action = GameFragmentDirections.actionGameToScore()
action.score = viewModel.score.value?:0
findNavController(this).navigate(action)
viewModel.onGameFinishComplete()
}
Se solicitado pelo Android Studio, importe androidx.navigation.fragment.NavHostFragment.findNavController
.
Bom trabalho! Seu aplicativo usa LiveData
para acionar um evento de jogo terminado para se comunicar do GameViewModel
para o fragmento do jogo que a lista de palavras está vazia. O fragmento do jogo então navega para o fragmento de pontuação.
Nesta tarefa, você altera a pontuação para um objeto LiveData
no ScoreViewModel
e anexa um observador a ele. Esta tarefa é semelhante ao que você fez quando adicionou LiveData
ao GameViewModel
.
Você faz essas alterações em ScoreViewModel
para integridade, de modo que todos os dados em seu aplicativo usem LiveData
.
ScoreViewModel
, altere o tipo de variável score
para MutableLiveData
. Renomeie-o por convenção para _score
e adicione uma propriedade de apoio.private val _score = MutableLiveData<Int>()
val score: LiveData<Int>
get() = _score
ScoreViewModel
, dentro do bloco init
, inicialize _score
. Você pode remover ou deixar o registro no bloco init
como desejar.init {
_score.value = finalScore
}
ScoreFragment
, dentro de onCreateView()
, após inicializar o viewModel
, anexe um observador para a pontuação LiveData
. Dentro da expressão lambda, defina o valor da pontuação para a vista do texto da pontuação. Remova o código que atribui diretamente a vista de texto com o valor de pontuação do ViewModel
.Código para adicionar:
viewModel.score.observe(viewLifecycleOwner, Observer { newScore ->
binding.scoreText.text = newScore.toString()
})
Código para remover:
binding.scoreText.text = viewModel.score.toString()
Quando solicitado pelo Android Studio, importe androidx.lifecycle.Observer
.
LiveData
e um observador para atualizar a partitura.Nesta tarefa, você adiciona um botão Play Again à tela de pontuação e implementa seu ouvinte de clique usando um evento LiveData
. O botão aciona um evento para navegar da tela de pontuação para a tela do jogo.
O código inicial do aplicativo inclui o botão Play Again, mas o botão está oculto.
res/layout/score_fragment.xml
, para o botão play_again_button
, altere a visibility
valor do atributo para visible
. <Button
android:id="@+id/play_again_button"
...
android:visibility="visible"
/>
ScoreViewModel
, adicione um objeto LiveData
para conter um Boolean
chamado _eventPlayAgain
. Este objeto é usado para salvar o evento LiveData
para navegar da tela de pontuação para a tela do jogo.private val _eventPlayAgain = MutableLiveData<Boolean>()
val eventPlayAgain: LiveData<Boolean>
get() = _eventPlayAgain
ScoreViewModel
, defina métodos para definir e redefinir o evento, _eventPlayAgain
. fun onPlayAgain() {
_eventPlayAgain.value = true
}
fun onPlayAgainComplete() {
_eventPlayAgain.value = false
}
ScoreFragment
, adicione um observador para eventPlayAgain
. Coloque o código no final de onCreateView()
, antes da instrução return
. Dentro da expressão lambda, navegue de volta para a tela do jogo e redefina eventPlayAgain
.viewModel.eventPlayAgain.observe(viewLifecycleOwner, Observer { playAgain ->
if (playAgain) {
findNavController().navigate(ScoreFragmentDirections.actionRestart())
viewModel.onPlayAgainComplete()
}
})
Importe androidx.navigation.fragment.findNavController
, quando solicitado pelo Android Studio.
ScoreFragment
, dentro de onCreateView()
, adicione um ouvinte de clique ao botão PlayAgain e chame viewModel
.onPlayAgain()
.binding.playAgainButton.setOnClickListener { viewModel.onPlayAgain() }
Bom trabalho! Você alterou a arquitetura de seu aplicativo para usar objetos LiveData
no ViewModel
e anexou observadores aos objetos LiveData
. LiveData
notifica objetos observadores quando o valor mantido pelo LiveData
muda.
Projeto Android Studio: GuessTheWord
LiveData
é uma classe de recipiente de dados observável que reconhece o ciclo de vida, um dos Componentes de arquitetura do Android.LiveData
para permitir que sua IU seja atualizada automaticamente quando os dados forem atualizados. LiveData
é observável, o que significa que um observador como uma atividade ou um fragmento pode ser notificado quando os dados mantidos pelo objeto LiveData
mudam. LiveData
contém dados; é um embrulho que pode ser usado com qualquer dado.LiveData
reconhece o ciclo de vida, o que significa que somente atualiza observadores que estão em um estado de ciclo de vida ativo, como STARTED
ou RESUMED
.ViewModel
para LiveData
ou MutableLiveData
.MutableLiveData
é um objeto LiveData
cujo valor pode ser alterado. MutableLiveData
é uma classe genérica, portanto, você precisa especificar o tipo de dados que ela contém.
LiveData
, use o método setValue()
na variável LiveData
.LiveData
dentro do ViewModel
deve ser editável. Fora do ViewModel
, o LiveData
deve ser legível. Isso pode ser implementado usando uma propriedade de apoio do Kotlin.LiveData
, use private
MutableLiveData
dentro do ViewModel
e retorne um LiveData
propriedade de apoio fora do ViewModel
. LiveData
segue um padrão de observador. O "observável" é o objeto LiveData
e os observadores são os métodos nos controladores de IU, como fragmentos. Sempre que os dados agrupados em LiveData
são alterados, os métodos do observador nos controladores de IU são notificados.
LiveData
observável, anexe um objeto observador à referência LiveData
nos observadores (como atividades e fragmentos) usando observe()
método. LiveData
pode ser usado para se comunicar do ViewModel
com os controladores de IU. Documentação para desenvolvimento em Android:
MutableLiveData
De outros:
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.
Como você encapsula o LiveData
armazenado em um ViewModel
para que objetos externos possam ler os dados sem poder atualizá-los?
ViewModel
, altere o tipo de dados dos dados para private
LiveData
. Use uma propriedade de apoio para expor dados somente leitura do tipo MutableLiveData
.ViewModel
, altere o tipo de dados dos dados para private
MutableLiveData
. Use uma propriedade de apoio para expor dados somente leitura do tipo LiveData
.private
MutableLiveData
. Use uma propriedade de apoio para expor dados somente leitura do tipo LiveData
.ViewModel
, altere o tipo de dados dos dados para LiveData
. Use uma propriedade de apoio para expor dados somente leitura do tipo LiveData
.LiveData
atualiza um controlador de IU (como um fragmento) se o controlador de IU estiver em qual dos seguintes estados?
No padrão de observador LiveData
, qual é o item observável (o que é observado)?
LiveData
ViewModel
Comece a próxima lição:
Para obter enlaces para outros tutoriais neste curso, consulte a página de destino dos tutoriais Fundamentos de Android em Kotlin.