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.
Créditos | Tela de jogo | Tela de pontuação |
Introdução
Neste codelab, você aprenderá sobre um dos Componentes da arquitetura do Android, ViewModel
:
- A classe
ViewModel
é usada para armazenar e gerenciar dados relacionados à IU considerando o ciclo de vida. A classeViewModel
permite que os dados sobrevivam às mudanças de configuração do dispositivo, como a rotação da tela e as mudanças na disponibilidade do teclado. - Use a classe
ViewModelFactory
para instanciar e retornar o objetoViewModel
que sobrevive às mudanças de configuração.
O que você já precisa saber
- Saber 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 destinos de app e transmitir dados entre destinos de navegação.
- 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 Android recomendada.
- Como usar as classes
Lifecycle
,ViewModel
eViewModelFactory
no seu app. - Como reter dados da IU após mudanças na configuração do dispositivo.
- O que é o padrão de design método de fábrica e como usá-lo.
- Como criar um objeto
ViewModel
usando a interfaceViewModelProvider.Factory
.
Atividades do laboratório
- Adicione um
ViewModel
ao app para salvar os dados dele e sobreviver às mudanças de configuração. - Use
ViewModelFactory
e o padrão de design do método de fábrica para instanciar um objetoViewModel
com parâmetros de construtor.
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.
Nesta tarefa, você fará o download e executará o app inicial, além de examinar o código.
Etapa 1: primeiros passos
- Faça o download do código inicial do GuessTheWord (em inglês) e abra o projeto no Android Studio.
- Execute o app em um emulador ou em um dispositivo Android.
- Toque nos botões. O botão Pular exibe a próxima palavra e diminui a pontuação em um, e o botão Ok mostra a próxima palavra e aumenta a pontuação em um. O botão Encerrar jogo não é implementado, então nada acontece quando você toca nele.
Etapa 2: seguir as instruções do código
- No Android Studio, explore o código para ter uma ideia de como o app funciona.
- Confira os arquivos descritos abaixo, que são particularmente importantes.
MainActivity.kt
Este 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 no app.
Fragmentos de IU
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
screen/title/TitleFragment.kt
O fragmento de título é a primeira tela exibida quando o app é iniciado. Um gerenciador de cliques é definido como o botão Jogar para navegar até a tela do jogo.
telas/jogo/GameFragment.kt.
Este é o fragmento principal, em que ocorre a maior parte da ação do jogo:
- As variáveis são definidas para a palavra atual e a pontuação atual.
- A
wordList
definida no métodoresetList()
é uma lista de palavras de exemplo a ser usada no jogo. - O método
onSkip()
é o gerenciador de cliques do botão Skip. Ele diminui a pontuação em 1 e exibe a próxima palavra usando o métodonextWord()
. - O método
onCorrect()
é o gerenciador de cliques do botão Got It. Esse método é implementado de forma semelhante aoonSkip()
. A única diferença é que esse método adiciona 1 à pontuação em vez de subtrair.
screen/score/ScoreFragment.kt.
ScoreFragment
é a tela final do jogo e exibe a pontuação final do jogador. Neste codelab, você vai adicionar a implementação para exibir essa tela e mostrar a pontuação final.
res/navigation/main_navigation.xml
O gráfico de navegação mostra como os fragmentos são conectados usando a navegação:
- No fragmento de título, o usuário pode navegar para o fragmento de jogo.
- No fragmento do jogo, o usuário pode navegar para o fragmento de pontuação.
- A partir do fragmento de pontuação, o usuário pode voltar para o fragmento de jogo.
Nesta tarefa, você vai encontrar problemas com o app inicial GuessTheWord.
- Execute o código inicial e jogue com algumas palavras, tocando em Pular ou Ok após cada palavra.
- Agora a tela do jogo mostra uma palavra e a pontuação atual. Gire o dispositivo ou emulador para mudar a orientação da tela. Observe que a pontuação atual é perdida.
- Execute o jogo com mais algumas palavras. Quando a tela do jogo for exibida com alguma pontuação, feche e abra novamente o app. O jogo será reiniciado desde o início, porque o estado do app não é salvo.
- Use algumas palavras para jogar e toque no botão Encerrar jogo. Observe que nada acontece.
Problemas no app:
- O app inicial não salva e restaura o estado do app 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 recuperá-lo. 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 de arquitetura de app que você aprenderá neste codelab.
Arquitetura de apps
A arquitetura de apps é uma forma de criar seus apps e as classes entre eles, de forma que o código seja organizado, tenha um 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 de arquitetura de apps Android, e você usa os Componentes da arquitetura do Android. A arquitetura de apps Android é semelhante ao padrão de arquitetura MVVM (modelo-visualização-visualização).
O app GuessTheWord segue o princípio de design de separação de conceitos e é dividido em classes, com cada classe abordando uma preocupação separada. Neste primeiro codelab da lição, as classes com que você trabalha são um controlador de IU, uma ViewModel
e uma ViewModelFactory
.
controlador de IU
Um controlador de IU é uma classe baseada em IU, como Activity
ou Fragment
. Um controlador de IU precisa conter apenas a lógica que processa as interações entre a IU e o sistema operacional, como a exibição de visualizações e a captura da entrada do usuário. Não inclua uma lógica de tomada de decisão, como a lógica que determina o texto a ser exibido, no controlador de IU.
No código inicial do GuessTheWord, os controladores de IU são os três fragmentos: GameFragment
, ScoreFragment,
e TitleFragment
. Seguindo o princípio de design de"separação de preocupações", o GameFragment
só é responsável por desenhar elementos do jogo na tela e saber quando o usuário toca nos botões. Quando o usuário toca em um botão, essas informações são transmitidas para o GameViewModel
.
ViewModel
Um ViewModel
mantém os dados a serem exibidos 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 IU. Nessa arquitetura, o ViewModel
toma as decisões.
O GameViewModel
armazena dados como o valor da pontuação, a lista de palavras e a palavra atual, porque esses são os dados a serem exibidos na tela. A 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
instancia objetos ViewModel
, com ou sem parâmetros do construtor.
Nos codelabs futuros, você aprenderá sobre outros componentes de arquitetura do Android relacionados aos controladores de IU e ao ViewModel
.
A classe ViewModel
foi projetada para armazenar e gerenciar os dados relacionados à IU. Neste app, cada ViewModel
está associado a um fragmento.
Nesta tarefa, você adicionará o primeiro ViewModel
ao app, o GameViewModel
da GameFragment
. Você também aprenderá o que significa que o ViewModel
reconhece 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 para oViewModel
.
Se você usar a versão mais recente da biblioteca, o app da solução será compilado como esperado. Se isso não acontecer, tente resolver o problema ou reverta para a versão mostrada abaixo.
//ViewModel
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
- Na pasta
screens/game/
do pacote, crie uma nova classe Kotlin com o nomeGameViewModel
. - Faça com que a classe
GameViewModel
estenda 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
, modifique 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 o GameViewModel ao fragmento do jogo
Um ViewModel
precisa ser associado a um controlador de IU. Para associá-los, crie uma referência ao ViewModel
no controlador de IU.
Nesta etapa, você criará uma referência do GameViewModel
no controlador de IU 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 a rotação da tela, os controladores de IU, como fragmentos, são recriados. No entanto, as instâncias 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
retornará umViewModel
(caso ele exista) ou um novo, se ele ainda não existir. - O
ViewModelProvider
cria uma instância doViewModel
em associação com o escopo fornecido (uma atividade ou um fragmento). - O
ViewModel
criado é mantido enquanto o escopo estiver ativo. Por exemplo, se o escopo for um fragmento, aViewModel
será retida até que o fragmento seja desconectado.
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()
, após a 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 de 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 do seu dispositivo ou emulador. A tela do jogo será aberta.
Como mostrado no Logcat, o métodoonCreateView()
doGameFragment
chama o métodoViewModelProviders.of()
para criar oGameViewModel
. Os log statements 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ãoViewModelProviders.of()
é chamado sempre. 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 saia do fragmento. O
GameFragment
será destruído. OGameViewModel
associado também será destruído, e o callbackonCleared()
será 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, ele é um bom lugar para dados que precisam sobreviver às mudanças de configuração:
- Coloque os dados a serem exibidos na tela e adicione um código para processá-los no
ViewModel
. - O
ViewModel
nunca pode conter referências a fragmentos, atividades ou visualizações, já que atividades, fragmentos e visualizações não sobrevivem a mudanças de configuração.
Para comparação, veja como os dados da IU do GameFragment
são processados no app inicial antes de você adicionar ViewModel
e depois de adicionar ViewModel
:
- Antes de adicionar o
ViewModel
:
quando o app passa por uma mudança de configuração, como uma rotação de tela, o fragmento de jogo é destruído e recriado. Os dados são perdidos. - Depois de adicionar o
ViewModel
e mover os dados da IU do fragmento do jogo para oViewModel
:
todos os dados que o fragmento precisa exibir agora sãoViewModel
. Quando o app passa por uma mudança de configuração, oViewModel
sobrevive e os dados são mantidos.
Nesta tarefa, você moverá os dados da IU do app para a classe GameViewModel
, com os métodos para processá-los. Isso é feito para que os dados sejam retidos durante as mudanças de configuração.
Etapa 1: mover campos e processamento de dados para o ViewModel
Mova os seguintes campos e métodos de dados do GameFragment
para o GameViewModel
:
- Mova os campos de dados
word
,score
ewordList
. Confira seword
escore
não sãoprivate
.
Não mova a variável de vinculaçãoGameFragmentBinding
, porque ela contém referências às visualizações. Essa variável é usada para inflar o layout, configurar os listeners de clique e exibir os dados na tela (responsabilidades do fragmento). - Mova os métodos
resetList()
enextWord()
. Esses métodos decidem qual palavra será mostrada na tela. - No método
onCreateView()
, mova as chamadas do método pararesetList()
enextWord()
para o blocoinit
doGameViewModel
.
Esses métodos precisam estar no blocoinit
, porque a lista de palavras precisa ser redefinida quando aViewModel
for criada, e não toda vez que o fragmento for criado. Você pode excluir o log statement no blocoinit
deGameFragment
.
Os gerenciadores de cliques onSkip()
e onCorrect()
no GameFragment
contêm código para processar os dados e atualizar a IU. O código para atualizar a IU precisa permanecer 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()
doGameFragment
para oGameViewModel
. - No
GameViewModel
, verifique se os métodosonSkip()
eonCorrect()
não sãoprivate
, porque você fará referência a esses métodos no fragmento.
Veja 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!")
}
}
Veja 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 as referências a gerenciadores de cliques e campos de dados no 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 exibem 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
, no 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 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.
Bom trabalho! Agora, todos os dados do app serão armazenados em um ViewModel
. Portanto, eles são retidos durante as mudanças de configuração.
Nesta tarefa, você implementará o listener de clique do botão End Game.
- No
GameFragment
, adicione um método com o nomeonEndGame()
. O métodoonEndGame()
será chamado quando o usuário tocar no botão Encerrar jogo.
private fun onEndGame() {
}
- No
GameFragment
, no métodoonCreateView()
, localize o código que define os listeners de cliques para os botões Got It e Skip. Logo abaixo dessas duas linhas, defina um listener de clique para o botão End Game. Use a variável de vinculação,binding
. Dentro do listener de clique, chame o métodoonEndGame()
.
binding.endGameButton.setOnClickListener { onEndGame() }
- No
GameFragment
, adicione um método com o nomegameFinished()
para navegar até a tela de pontuação do app. Transmita a pontuação como um argumento, usando 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 navegue por algumas palavras. Toque no botão Encerrar jogo. O app navegará para a tela de pontuação, mas a pontuação final não será exibida. Isso será corrigido na próxima tarefa.
Quando o usuário termina o jogo, o ScoreFragment
não mostra a pontuação. Você quer que um ViewModel
mantenha a pontuação para ser exibida pelo ScoreFragment
. Você transmitirá o valor da pontuação durante a inicialização da ViewModel
usando o padrão de método de fábrica.
O padrão de método de fábrica é um padrão de design criativo 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ê 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 nova classe do Kotlin com o nomeScoreViewModel
. Essa classe será aViewModel
do fragmento de pontuação. - Estenda a classe
ScoreViewModel
daViewModel.
. 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 do Kotlin com o nomeScoreViewModelFactory
. 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, modifique o métodocreate()
. No métodocreate()
, retorne o objetoScoreViewModel
recém-construído.
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
- No
ScoreFragment
, no métodoonCreateView()
, depois de inicializar a variávelbinding
, inicialize oviewModelFactory
. Use aScoreViewModelFactory
. Transmita a pontuação final do pacote de argumentos como um parâmetro de construtor para oScoreViewModelFactory()
.
viewModelFactory = ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(arguments!!).score)
- No
onCreateView(
, depois de inicializar oviewModelFactory
, inicialize o objetoviewModel
. Chame o métodoViewModelProviders.of()
e transmita o contexto do fragmento de pontuação associado eviewModelFactory
. Isso 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. Navegue por algumas ou todas as palavras e toque em Encerrar jogo. O fragmento de pontuação agora exibe a pontuação final.
- Opcional: verifique os registros
ScoreViewModel
no Logcat filtrando porScoreViewModel
. O valor da pontuação será exibido.
2019-02-07 10:50:18.328 com.example.android.guesstheword I/ScoreViewModel: Final score is 15
Nesta tarefa, você implementou o ScoreFragment
para usar o ViewModel
. Você também aprendeu a criar um construtor parametrizado para um ViewModel
usando a interface ViewModelFactory
.
Parabéns! Você mudou a arquitetura do seu 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
- As diretrizes de arquitetura de apps do Android recomendam separar classes que tenham responsabilidades diferentes.
- Um controlador de IU é uma classe baseada em IU, como a
Activity
ou oFragment
. Os controladores de IU precisam conter somente a lógica que processa as interações entre a IU e o sistema operacional. Eles não devem conter dados a serem exibidos na IU. Coloque esses dados em umaViewModel
. - A classe
ViewModel
armazena e gerencia dados relacionados à IU. 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 IU com as instâncias do ViewModel
que armazenam dados:
Controle de IU | ViewModel |
Um exemplo de controlador de IU é a | Um exemplo de |
Não contém dados a serem exibidos na IU. | Contém dados que o controlador de IU exibe na IU. |
Contém o código para exibir dados e o código de evento do usuário, como listeners de cliques. | Contém código para processamento de dados. |
Destruídos e recriados durante cada mudança de configuração. | Destruído somente quando o controlador de IU associado é removido permanentemente. Para uma atividade, quando a atividade é concluída ou para um fragmento, quando o fragmento é removido. |
Contém visualizações. | Nunca deve conter referências a atividades, fragmentos ou visualizações, porque elas 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 IU associado. |
Curso da Udacity:
- Como desenvolver apps Android com Kotlin (link em inglês)
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 de arquitetura MVVM (model-view-viewmodel).
- Princípio 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 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
Para evitar a perda de dados durante uma mudança na configuração do dispositivo, é preciso salvar os dados do app em qual classe?
ViewModel
LiveData
Fragment
Activity
Pergunta 2
Uma ViewModel
nunca 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.
- Atualização dos dados exibidos na tela.
- Recebimento de notificações quando os dados do aplicativo são alterados.
Inicie a próxima lição:
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.