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.
Créditos | Tela do jogo | Tela de pontuação |
Introdução
Neste codelab, você vai aprender sobre um dos componentes da arquitetura do Android, o ViewModel
:
- Você usa a classe
ViewModel
para armazenar e gerenciar dados relacionados à interface considerando o ciclo de vida. A classeViewModel
permite que os dados sobrevivam a mudanças na configuração do dispositivo, como rotações de tela e mudanças na disponibilidade do teclado. - Use a classe
ViewModelFactory
para instanciar e retornar o objetoViewModel
que sobrevive a mudanças de configuração.
O que você já precisa saber
- Como criar apps Android básicos em Kotlin.
- Como usar o gráfico de navegação para implementar a navegação no app.
- Como adicionar código para navegar entre os destinos do app e transmitir dados entre eles.
- Como os ciclos de vida de atividades e fragmentos funcionam.
- Como adicionar informações de registro a um app e ler registros usando o Logcat no Android Studio.
O que você vai aprender
- Como usar a arquitetura de apps recomendada para Android.
- Como usar as classes
Lifecycle
,ViewModel
eViewModelFactory
no seu app. - Como armazenar dados da interface após mudanças na configuração do dispositivo.
- O que é o padrão de projeto método de fábrica e como usá-lo.
- Como criar um objeto
ViewModel
usando a interfaceViewModelProvider.Factory
.
Atividades deste laboratório
- Adicione um
ViewModel
ao app para salvar os dados dele e garantir que eles sobrevivam às mudanças de configuração. - Use
ViewModelFactory
e o padrão de projeto de método de fábrica para instanciar um objetoViewModel
com parâmetros de construtor.
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.
Nesta tarefa, você vai baixar e executar o app inicial e examinar o código.
Etapa 1: primeiros passos
- Faça o download do código inicial do GuessTheWord (link em inglês) e abra o projeto no Android Studio.
- Execute o app em um dispositivo Android ou em um emulador.
- Toque nos botões. O botão Pular mostra a próxima palavra e diminui a pontuação em um, e o botão Entendi mostra a próxima palavra e aumenta a pontuação em um. O botão Encerrar jogo não está implementado, então nada acontece quando você toca nele.
Etapa 2: fazer um tutorial de código
- No Android Studio, analise o código para entender como o app funciona.
- Confira os arquivos descritos abaixo, que são particularmente importantes.
MainActivity.kt
Esse arquivo contém apenas o código padrão gerado pelo modelo.
res/layout/main_activity.xml
Esse arquivo contém o layout principal do app. O NavHostFragment
hospeda os outros fragmentos à medida que o usuário navega pelo app.
Fragmentos de interface
O código inicial tem três fragmentos em três pacotes diferentes no pacote com.example.android.guesstheword.screens
:
title/TitleFragment
para a tela de títulogame/GameFragment
para a tela do jogoscore/ScoreFragment
para a tela de pontuação
screens/title/TitleFragment.kt
O fragmento de título é a primeira tela exibida quando o app é iniciado. Um gerenciador de cliques é definido para o botão Jogar, para navegar até a tela do jogo.
screens/game/GameFragment.kt
Esse é o fragmento principal, onde a maior parte da ação do jogo acontece:
- As variáveis são definidas para a palavra atual e a pontuação atual.
- O
wordList
definido no métodoresetList()
é uma lista de exemplo de palavras a serem usadas no jogo. - O método
onSkip()
é o gerenciador de cliques do botão Pular. Ele diminui a pontuação em 1 e mostra a próxima palavra usando o métodonextWord()
. - O método
onCorrect()
é o gerenciador de cliques do botão Entendi. Esse método é implementado de maneira semelhante ao métodoonSkip()
. A única diferença é que esse método adiciona 1 à pontuação em vez de subtrair.
screens/score/ScoreFragment.kt
ScoreFragment
é a tela final do jogo e mostra a pontuação final do jogador. Neste codelab, você vai adicionar a implementação para mostrar essa tela e a pontuação final.
res/navigation/main_navigation.xml
O gráfico de navegação mostra como os fragmentos estão conectados pela navegação:
- No fragmento de título, o usuário pode navegar até o fragmento de jogo.
- No fragmento do jogo, o usuário pode navegar até o fragmento da pontuação.
- No fragmento de pontuação, o usuário pode voltar ao fragmento do jogo.
Nesta tarefa, você vai encontrar problemas com o app inicial GuessTheWord.
- Execute o código inicial e jogue uma partida com algumas palavras, tocando em Pular ou Entendi após cada palavra.
- A tela do jogo agora mostra uma palavra e a pontuação atual. Gire o dispositivo ou emulador para mudar a orientação da tela. A pontuação atual é perdida.
- Jogue com mais algumas palavras. Quando a tela do jogo aparecer com uma pontuação, feche e abra o app novamente. O jogo vai reiniciar do começo porque o estado do app não foi salvo.
- Jogue uma partida com algumas palavras e toque no botão Encerrar jogo. Nada acontece.
Problemas no app:
- O app inicial não salva e restaura o estado dele durante mudanças de configuração, como quando a orientação do dispositivo muda ou quando o app é desligado e reiniciado.
Você pode resolver esse problema usando o callbackonSaveInstanceState()
. No entanto, o uso do métodoonSaveInstanceState()
exige que você escreva um código extra para salvar o estado em um pacote e implementar a lógica para acessar esse estado. Além disso, a quantidade de dados que pode ser armazenada é mínima. - A tela do jogo não navega para a tela de pontuação quando o usuário toca no botão Encerrar jogo.
É possível resolver esses problemas usando os componentes da arquitetura do app que você vai aprender neste codelab.
Arquitetura de apps
A arquitetura de apps é uma maneira de projetar as classes dos apps e as relações entre elas para que o código seja organizado, tenha bom desempenho em cenários específicos e seja fácil de trabalhar. Neste conjunto de quatro codelabs, as melhorias feitas no app GuessTheWord seguem as diretrizes da arquitetura de apps Android, e você usa os Componentes de arquitetura do Android. A arquitetura de apps Android é semelhante ao padrão arquitetônico MVVM (model-view-viewmodel).
O app GuessTheWord segue o princípio de design de separação de conceitos e é dividido em classes, cada uma abordando um conceito separado. Neste primeiro codelab da lição, as classes com que você vai trabalhar são um controlador de interface, um ViewModel
e um ViewModelFactory
.
Controlador de interface
Um controlador de UI é uma classe baseada em UI, como Activity
ou Fragment
. Um controlador de UI precisa conter apenas a lógica que processa as interações entre a UI e o sistema operacional, como mostrar visualizações e capturar entradas do usuário. Não coloque lógica de tomada de decisão, como a que determina o texto a ser exibido, no controlador de UI.
No código inicial do GuessTheWord, os controladores de UI são os três fragmentos: GameFragment
, ScoreFragment,
e TitleFragment
. Seguindo o princípio de design de "separação de responsabilidades", o GameFragment
é responsável apenas por desenhar elementos do jogo na tela e saber quando o usuário toca nos botões, e nada mais. Quando o usuário toca em um botão, essas informações são transmitidas ao GameViewModel
.
ViewModel
Um ViewModel
contém dados que serão mostrados em um fragmento ou uma atividade associada ao ViewModel
. Um ViewModel
pode fazer cálculos e transformações simples nos dados para preparar os dados a serem exibidos pelo controlador da interface. Nessa arquitetura, o ViewModel
toma as decisões.
O GameViewModel
contém dados como o valor da pontuação, a lista de palavras e a palavra atual, porque são os dados que serão mostrados na tela. O GameViewModel
também contém a lógica de negócios para realizar cálculos simples e decidir qual é o estado atual dos dados.
ViewModelFactory
Um ViewModelFactory
cria instâncias de objetos ViewModel
, com ou sem parâmetros de construtor.
Em codelabs futuros, você vai aprender sobre outros componentes da arquitetura do Android relacionados a controladores de interface e ViewModel
.
A classe ViewModel
foi projetada para armazenar e gerenciar os dados relacionados à interface. Neste app, cada ViewModel
está associado a um fragmento.
Nesta tarefa, você vai adicionar o primeiro ViewModel
ao app, o GameViewModel
do GameFragment
. Você também vai aprender o que significa o fato de ViewModel
ser compatível com o ciclo de vida.
Etapa 1: adicionar a classe GameViewModel
- Abra o arquivo
build.gradle(module:app)
. No blocodependencies
, adicione a dependência do Gradle paraViewModel
.
. Se você usar a versão mais recente da biblioteca, o app de solução será compilado conforme o esperado. Se não for, tente resolver o problema ou volte para a versão mostrada abaixo.
//ViewModel
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
- Na pasta do pacote
screens/game/
, crie uma classe Kotlin chamadaGameViewModel
. - Faça a classe
GameViewModel
estender a classe abstrataViewModel
. - Para ajudar você a entender melhor como o
ViewModel
reconhece o ciclo de vida, adicione um blocoinit
com uma instruçãolog
.
class GameViewModel : ViewModel() {
init {
Log.i("GameViewModel", "GameViewModel created!")
}
}
Etapa 2: substituir onCleared() e adicionar registros
O ViewModel
é destruído quando o fragmento associado é desanexado ou quando a atividade é concluída. Logo antes do ViewModel
ser destruído, o callback onCleared()
é chamado para limpar os recursos.
- Na classe
GameViewModel
, substitua o métodoonCleared()
. - Adicione um log statement ao método
onCleared()
para monitorar o ciclo de vida doGameViewModel
.
override fun onCleared() {
super.onCleared()
Log.i("GameViewModel", "GameViewModel destroyed!")
}
Etapa 3: associar GameViewModel ao fragmento do jogo
Um ViewModel
precisa estar associado a um controlador de UI. Para associar os dois, crie uma referência ao ViewModel
no controlador de UI.
Nesta etapa, você vai criar uma referência do GameViewModel
no controlador de UI correspondente, que é o GameFragment
.
- Na classe
GameFragment
, adicione um campo do tipoGameViewModel
no nível superior como uma variável de classe.
private lateinit var viewModel: GameViewModel
Etapa 4: inicializar o ViewModel
Durante mudanças de configuração, como rotações de tela, os controladores de UI, como fragmentos, são recriados. No entanto, as instâncias de ViewModel
sobrevivem. Se você criar a instância ViewModel
usando a classe ViewModel
, um novo objeto será criado sempre que o fragmento for recriado. Em vez disso, crie a instância ViewModel
usando um ViewModelProvider
.
Como o ViewModelProvider
funciona:
- O
ViewModelProvider
retorna umViewModel
existente, se houver, ou cria um novo, se ele ainda não existir. - O
ViewModelProvider
cria uma instânciaViewModel
em associação com o escopo especificado (uma atividade ou um fragmento). - O
ViewModel
criado é mantido enquanto o escopo estiver ativo. Por exemplo, se o escopo for um fragmento, oViewModel
será mantido até que o fragmento seja desanexado.
Inicialize o ViewModel
usando o método ViewModelProviders.of()
para criar um ViewModelProvider
:
- Na classe
GameFragment
, inicialize a variávelviewModel
. Coloque esse código dentro deonCreateView()
, depois da definição da variável de vinculação. Use o métodoViewModelProviders.of()
e transmita o contextoGameFragment
associado e a classeGameViewModel
. - Acima da inicialização do objeto
ViewModel
, adicione um log statement para registrar a chamada do métodoViewModelProviders.of()
.
Log.i("GameFragment", "Called ViewModelProviders.of")
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)
- Execute o app. No Android Studio, abra o painel Logcat e filtre por
Game
. Toque no botão Play no dispositivo ou emulador. A tela do jogo é aberta.
Como mostrado no Logcat, o métodoonCreateView()
doGameFragment
chama o métodoViewModelProviders.of()
para criar oGameViewModel
. As instruções de registro que você adicionou aoGameFragment
e aoGameViewModel
aparecem no Logcat.
- Ative a configuração de giro automático do dispositivo ou emulador e mude a orientação da tela algumas vezes. O
GameFragment
é destruído e recriado todas as vezes, então oViewModelProviders.of()
é chamado todas as vezes. mas oGameViewModel
é criado apenas uma vez e não é recriado nem destruído para cada chamada.
I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel created! I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of
- Saia do jogo ou navegue para fora do fragmento do jogo. O
GameFragment
será destruído. OGameViewModel
associado também é destruído, e o callbackonCleared()
é chamado.
I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel created! I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel destroyed!
O ViewModel
sobrevive a mudanças de configuração. Por isso, é um bom lugar para dados que precisam sobreviver a essas mudanças:
- Coloque os dados a serem mostrados na tela e o código para processar esses dados no
ViewModel
. - O
ViewModel
nunca pode conter referências a fragmentos, atividades ou visualizações, porque eles não persistem após mudanças na configuração.
Para fins de comparação, veja como os dados da interface GameFragment
são processados no app inicial antes e depois de adicionar ViewModel
:ViewModel
- Antes de adicionar
ViewModel
:
Quando o app passa por uma mudança de configuração, como uma rotação de tela, o fragmento do jogo é destruído e recriado. Os dados são perdidos. - Depois de adicionar
ViewModel
e mover os dados da interface do fragmento do jogo para oViewModel
:
. Todos os dados que o fragmento precisa mostrar agora são oViewModel
. Quando o app passa por uma mudança de configuração, oViewModel
sobrevive e os dados são mantidos.
Nesta tarefa, você vai mover os dados da interface do app para a classe GameViewModel
, junto com os métodos para processar os dados. Isso é feito para que os dados sejam mantidos durante as mudanças de configuração.
Etapa 1: mover campos de dados e processamento de dados para o ViewModel
Mova os seguintes campos de dados e métodos de GameFragment
para GameViewModel
:
- Mova os campos de dados
word
,score
ewordList
. Verifique seword
escore
não sãoprivate
.
Não mova a variável de vinculação,GameFragmentBinding
, porque ela contém referências às visualizações. Essa variável é usada para inflar o layout, configurar os listeners de clique e mostrar os dados na tela, que são responsabilidades do fragmento. - Mova os métodos
resetList()
enextWord()
. Esses métodos decidem qual palavra mostrar na tela. - No método
onCreateView()
, mova as chamadas de método pararesetList()
enextWord()
para o blocoinit
doGameViewModel
.
Esses métodos precisam estar no blocoinit
, porque você precisa redefinir a lista de palavras quando oViewModel
é criado, não sempre que o fragmento é criado. Você pode excluir a instrução de registro no blocoinit
deGameFragment
.
Os manipuladores de cliques onSkip()
e onCorrect()
no GameFragment
contêm código para processar os dados e atualizar a interface. O código para atualizar a interface precisa ficar no fragmento, mas o código para processar os dados precisa ser movido para o ViewModel
.
Por enquanto, coloque os métodos idênticos nos dois lugares:
- Copie os métodos
onSkip()
eonCorrect()
daGameFragment
para aGameViewModel
. - No
GameViewModel
, verifique se os métodosonSkip()
eonCorrect()
não sãoprivate
, porque você vai referenciar esses métodos do fragmento.
Confira o código da classe GameViewModel
após a refatoração:
class GameViewModel : ViewModel() {
// The current word
var word = ""
// The current score
var score = 0
// The list of words - the front of the list is the next word to guess
private lateinit var wordList: MutableList<String>
/**
* Resets the list of words and randomizes the order
*/
private fun resetList() {
wordList = mutableListOf(
"queen",
"hospital",
"basketball",
"cat",
"change",
"snail",
"soup",
"calendar",
"sad",
"desk",
"guitar",
"home",
"railway",
"zebra",
"jelly",
"car",
"crow",
"trade",
"bag",
"roll",
"bubble"
)
wordList.shuffle()
}
init {
resetList()
nextWord()
Log.i("GameViewModel", "GameViewModel created!")
}
/**
* Moves to the next word in the list
*/
private fun nextWord() {
if (!wordList.isEmpty()) {
//Select and remove a word from the list
word = wordList.removeAt(0)
}
updateWordText()
updateScoreText()
}
/** Methods for buttons presses **/
fun onSkip() {
if (!wordList.isEmpty()) {
score--
}
nextWord()
}
fun onCorrect() {
if (!wordList.isEmpty()) {
score++
}
nextWord()
}
override fun onCleared() {
super.onCleared()
Log.i("GameViewModel", "GameViewModel destroyed!")
}
}
Confira o código da classe GameFragment
após a refatoração:
/**
* Fragment where the game is played
*/
class GameFragment : Fragment() {
private lateinit var binding: GameFragmentBinding
private lateinit var viewModel: GameViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate view and obtain an instance of the binding class
binding = DataBindingUtil.inflate(
inflater,
R.layout.game_fragment,
container,
false
)
Log.i("GameFragment", "Called ViewModelProviders.of")
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)
binding.correctButton.setOnClickListener { onCorrect() }
binding.skipButton.setOnClickListener { onSkip() }
updateScoreText()
updateWordText()
return binding.root
}
/** Methods for button click handlers **/
private fun onSkip() {
if (!wordList.isEmpty()) {
score--
}
nextWord()
}
private fun onCorrect() {
if (!wordList.isEmpty()) {
score++
}
nextWord()
}
/** Methods for updating the UI **/
private fun updateWordText() {
binding.wordText.text = word
}
private fun updateScoreText() {
binding.scoreText.text = score.toString()
}
}
Etapa 2: atualizar referências a manipuladores de cliques e campos de dados em GameFragment
- No
GameFragment
, atualize os métodosonSkip()
eonCorrect()
. Remova o código para atualizar a pontuação e chame os métodosonSkip()
eonCorrect()
correspondentes emviewModel
. - Como você moveu o método
nextWord()
para oViewModel
, o fragmento do jogo não pode mais acessá-lo.
EmGameFragment
, nos métodosonSkip()
eonCorrect()
, substitua a chamada paranextWord()
porupdateScoreText()
eupdateWordText()
. Esses métodos mostram os dados na tela.
private fun onSkip() {
viewModel.onSkip()
updateWordText()
updateScoreText()
}
private fun onCorrect() {
viewModel.onCorrect()
updateScoreText()
updateWordText()
}
- No
GameFragment
, atualize as variáveisscore
eword
para usar as variáveisGameViewModel
, porque elas agora estão noGameViewModel
.
private fun updateWordText() {
binding.wordText.text = viewModel.word
}
private fun updateScoreText() {
binding.scoreText.text = viewModel.score.toString()
}
- No
GameViewModel
, dentro do métodonextWord()
, remova as chamadas para os métodosupdateWordText()
eupdateScoreText()
. Esses métodos agora estão sendo chamados doGameFragment
. - Crie o app e verifique se não há erros. Se houver erros, limpe e recrie o projeto.
- Execute o app e jogue uma partida com algumas palavras. Na tela do jogo, gire o dispositivo. A pontuação e a palavra atuais são mantidas após a mudança de orientação.
Muito bem! Agora, todos os dados do app são armazenados em um ViewModel
, então eles são mantidos durante as mudanças de configuração.
Nesta tarefa, você vai implementar o listener de clique para o botão End Game.
- Em
GameFragment
, adicione um método chamadoonEndGame()
. O métodoonEndGame()
será chamado quando o usuário tocar no botão Encerrar jogo.
private fun onEndGame() {
}
- Em
GameFragment
, dentro do métodoonCreateView()
, localize o código que define listeners de clique para os botões Entendi e Pular. Logo abaixo dessas duas linhas, defina um listener de clique para o botão End Game. Use a variável de vinculação,binding
. No listener de cliques, chame o métodoonEndGame()
.
binding.endGameButton.setOnClickListener { onEndGame() }
- Em
GameFragment
, adicione um método chamadogameFinished()
para navegar até a tela de pontuação. Transmita a pontuação como um argumento usando o Safe Args.
/**
* Called when the game is finished
*/
private fun gameFinished() {
Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
val action = GameFragmentDirections.actionGameToScore()
action.score = viewModel.score
NavHostFragment.findNavController(this).navigate(action)
}
- No método
onEndGame()
, chame o métodogameFinished()
.
private fun onEndGame() {
gameFinished()
}
- Execute o app, jogue e passe por algumas palavras. Toque no botão Encerrar jogo . O app navega até a tela de pontuação, mas a pontuação final não é mostrada. Isso será corrigido na próxima tarefa.
Quando o usuário termina o jogo, o ScoreFragment
não mostra a pontuação. Você quer um ViewModel
para manter a pontuação a ser mostrada pelo ScoreFragment
. Você vai transmitir o valor da pontuação durante a inicialização do ViewModel
usando o padrão de método de fábrica.
O padrão de método de fábrica é um padrão de projeto de criação que usa métodos de fábrica para criar objetos. Um método de fábrica é um método que retorna uma instância da mesma classe.
Nesta tarefa, você vai criar um ViewModel
com um construtor parametrizado para o fragmento de pontuação e um método de fábrica para instanciar o ViewModel
.
- No pacote
score
, crie uma classe Kotlin chamadaScoreViewModel
. Essa classe será aViewModel
do fragmento de pontuação. - Estenda a classe
ScoreViewModel
deViewModel.
. Adicione um parâmetro construtor para a pontuação final. Adicione um blocoinit
com um log statement. - Na classe
ScoreViewModel
, adicione uma variável chamadascore
para salvar a pontuação final.
class ScoreViewModel(finalScore: Int) : ViewModel() {
// The final score
var score = finalScore
init {
Log.i("ScoreViewModel", "Final score is $finalScore")
}
}
- No pacote
score
, crie outra classe Kotlin chamadaScoreViewModelFactory
. Essa classe será responsável por instanciar o objetoScoreViewModel
. - Estenda a classe
ScoreViewModelFactory
deViewModelProvider.Factory
. Adicione um parâmetro construtor para a pontuação final.
class ScoreViewModelFactory(private val finalScore: Int) : ViewModelProvider.Factory {
}
- Em
ScoreViewModelFactory
, o Android Studio mostra um erro sobre um membro abstrato não implementado. Para resolver o erro, substitua o métodocreate()
. No métodocreate()
, retorne o objetoScoreViewModel
recém-criado.
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(ScoreViewModel::class.java)) {
return ScoreViewModel(finalScore) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
- Em
ScoreFragment
, crie variáveis de classe paraScoreViewModel
eScoreViewModelFactory
.
private lateinit var viewModel: ScoreViewModel
private lateinit var viewModelFactory: ScoreViewModelFactory
- Em
ScoreFragment
, dentro deonCreateView()
, depois de inicializar a variávelbinding
, inicialize oviewModelFactory
. Use aScoreViewModelFactory
. Transmita a pontuação final do pacote de argumentos como um parâmetro do construtor para oScoreViewModelFactory()
.
viewModelFactory = ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(arguments!!).score)
- Em
onCreateView(
, depois de inicializarviewModelFactory
, inicialize o objetoviewModel
. Chame o métodoViewModelProviders.of()
, transmita o contexto do fragmento de pontuação associado eviewModelFactory
. Isso vai criar o objetoScoreViewModel
usando o método de fábrica definido na classeviewModelFactory
..
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ScoreViewModel::class.java)
- No método
onCreateView()
, depois de inicializar oviewModel
, defina o texto da visualizaçãoscoreText
como a pontuação final definida noScoreViewModel
.
binding.scoreText.text = viewModel.score.toString()
- Execute o app e jogue uma partida. Passe por algumas ou todas as palavras e toque em Encerrar jogo. O fragmento de pontuação agora mostra a pontuação final.
- Opcional: verifique os registros
ScoreViewModel
no Logcat filtrando porScoreViewModel
. O valor da pontuação precisa ser mostrado.
2019-02-07 10:50:18.328 com.example.android.guesstheword I/ScoreViewModel: Final score is 15
Nesta tarefa, você implementou ScoreFragment
para usar ViewModel
. Você também aprendeu a criar um construtor parametrizado para um ViewModel
usando a interface ViewModelFactory
.
Parabéns! Você mudou a arquitetura do app para usar um dos Componentes da arquitetura do Android, ViewModel
. Você resolveu o problema do ciclo de vida do app, e agora os dados do jogo sobrevivem às mudanças de configuração. Você também aprendeu a criar um construtor parametrizado para criar um ViewModel
usando a interface ViewModelFactory
.
Projeto do Android Studio: GuessTheWord (link em inglês)
- As diretrizes de arquitetura de apps do Android recomendam separar classes que têm responsabilidades diferentes.
- Um controlador de UI é uma classe baseada em UI, como
Activity
ouFragment
. Os controladores de UI precisam conter somente a lógica que processa as interações entre a UI e o sistema operacional. Eles não podem conter dados a serem exibidos na UI. Coloque esses dados em umViewModel
. - A classe
ViewModel
armazena e gerencia dados relacionados à interface. A classeViewModel
permite que os dados sobrevivam às mudanças de configuração, como a rotação da tela. - O
ViewModel
é um dos Componentes da arquitetura do Android recomendados. ViewModelProvider.Factory
é uma interface que pode ser usada para criar um objetoViewModel
.
A tabela abaixo compara os controladores de interface com as instâncias ViewModel
que contêm dados para eles:
Controlador de interface | ViewModel |
Um exemplo de controlador de UI é o | Um exemplo de |
Não contém dados para serem mostrados na interface. | Contém dados que o controlador de UI mostra na interface. |
Contém código para mostrar dados e código de eventos do usuário, como listeners de cliques. | Contém código para processamento de dados. |
Destruído e recriado durante cada mudança de configuração. | Destruído apenas quando o controlador de interface associado desaparece permanentemente. No caso de uma atividade, quando ela termina, e no caso de um fragmento, quando ele é desanexado. |
Contém visualizações. | Nunca deve conter referências a atividades, fragmentos ou visualizações, porque eles não sobrevivem a mudanças de configuração, mas o |
Contém uma referência ao | Não contém nenhuma referência ao controlador de interface associado. |
Curso da Udacity:
Documentação do desenvolvedor Android:
- Visão geral do ViewModel
- Como gerenciar ciclos de vida com componentes que os reconhecem
- Guia para a arquitetura do app
ViewModelProvider
ViewModelProvider.Factory
Outro:
- Padrão arquitetônico MVVM (model-view-viewmodel).
- Princípio de design de separação de conceitos (SoC, na sigla em inglês)
- Padrão de método de fábrica
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
Para evitar a perda de dados durante uma mudança na configuração do dispositivo, é preciso salvar dados do app em qual classe?
ViewModel
LiveData
Fragment
Activity
Pergunta 2
Um ViewModel
não pode conter referências a fragmentos, atividades ou visualizações. Verdadeiro ou falso?
- Verdadeiro
- Falso
Pergunta 3
Quando um ViewModel
é destruído?
- Quando o controlador de IU associado é destruído e recriado durante uma mudança na orientação do dispositivo.
- Em uma mudança de orientação.
- Quando o controlador de IU associado é concluído (se for uma atividade) ou removido (se for um fragmento).
- Quando o usuário pressiona o botão "Voltar".
Pergunta 4
Para que serve a interface ViewModelFactory
?
- Instanciar um objeto
ViewModel
. - Reter dados durante mudanças de orientação.
- Atualizar os dados mostrados na tela.
- Receber notificações quando os dados do app são modificados.
Comece a próxima lição:
Para acessar links de outros codelabs neste curso, consulte a página inicial dos codelabs de conceitos básicos do Kotlin para Android.