Conceitos básicos do Kotlin para Android 05.1: ViewModel e ViewModelFactory

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 classe ViewModel 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 objeto ViewModel 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

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 objeto ViewModel 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

  1. Faça o download do código inicial do GuessTheWord (em inglês) e abra o projeto no Android Studio.
  2. Execute o app em um emulador ou em um dispositivo Android.
  3. 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

  1. No Android Studio, explore o código para ter uma ideia de como o app funciona.
  2. 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ítulo
  • game/GameFragment para a tela do jogo
  • score/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étodo resetList() é 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étodo nextWord().
  • O método onCorrect() é o gerenciador de cliques do botão Got It. Esse método é implementado de forma semelhante ao onSkip(). 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.

  1. Execute o código inicial e jogue com algumas palavras, tocando em Pular ou Ok após cada palavra.
  2. 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.
  3. 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.
  4. 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 callback onSaveInstanceState(). No entanto, o uso do método onSaveInstanceState() 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

  1. Abra o arquivo build.gradle(module:app). No bloco dependencies, adicione a dependência do Gradle para o ViewModel.

    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'
  1. Na pasta screens/game/ do pacote, crie uma nova classe Kotlin com o nome GameViewModel.
  2. Faça com que a classe GameViewModel estenda a classe abstrata ViewModel.
  3. Para ajudar você a entender melhor como o ViewModel reconhece o ciclo de vida, adicione um bloco init com uma instrução log.
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.

  1. Na classe GameViewModel, modifique o método onCleared().
  2. Adicione um log statement ao método onCleared() para monitorar o ciclo de vida do GameViewModel.
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.

  1. Na classe GameFragment, adicione um campo do tipo GameViewModel 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á um ViewModel (caso ele exista) ou um novo, se ele ainda não existir.
  • O ViewModelProvider cria uma instância do ViewModel 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, a ViewModel será retida até que o fragmento seja desconectado.

Inicialize o ViewModel usando o método ViewModelProviders.of() para criar um ViewModelProvider:

  1. Na classe GameFragment, inicialize a variável viewModel. Coloque esse código dentro de onCreateView(), após a definição da variável de vinculação. Use o método ViewModelProviders.of() e transmita o contexto GameFragment associado e a classe GameViewModel.
  2. Acima da inicialização do objeto ViewModel, adicione um log statement para registrar a chamada de método ViewModelProviders.of().
Log.i("GameFragment", "Called ViewModelProviders.of")
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)
  1. 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étodo onCreateView() do GameFragment chama o método ViewModelProviders.of() para criar o GameViewModel. Os log statements que você adicionou ao GameFragment e ao GameViewModel aparecem no Logcat.

  1. 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 ViewModelProviders.of() é chamado sempre. Mas o GameViewModel é 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
  1. Saia do jogo ou saia do fragmento. O GameFragment será destruído. O GameViewModel associado também será destruído, e o callback onCleared() 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 o ViewModel:
    todos os dados que o fragmento precisa exibir agora são ViewModel. Quando o app passa por uma mudança de configuração, o ViewModel 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:

  1. Mova os campos de dados word, score e wordList. Confira se word e score não são private.

    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 exibir os dados na tela (responsabilidades do fragmento).
  2. Mova os métodos resetList() e nextWord(). Esses métodos decidem qual palavra será mostrada na tela.
  3. No método onCreateView(), mova as chamadas do método para resetList() e nextWord() para o bloco init do GameViewModel.

    Esses métodos precisam estar no bloco init, porque a lista de palavras precisa ser redefinida quando a ViewModel for criada, e não toda vez que o fragmento for criado. Você pode excluir o log statement no bloco init de GameFragment.

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:

  1. Copie os métodos onSkip() e onCorrect() do GameFragment para o GameViewModel.
  2. No GameViewModel, verifique se os métodos onSkip() e onCorrect() não são private, 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

  1. No GameFragment, atualize os métodos onSkip() e onCorrect(). Remova o código para atualizar a pontuação e chame os métodos onSkip() e onCorrect() correspondentes em viewModel.
  2. Como você moveu o método nextWord() para o ViewModel, o fragmento do jogo não pode mais acessá-lo.

    Em GameFragment, nos métodos onSkip() e onCorrect(), substitua a chamada para nextWord() por updateScoreText() e updateWordText(). Esses métodos exibem os dados na tela.
private fun onSkip() {
   viewModel.onSkip()
   updateWordText()
   updateScoreText()
}
private fun onCorrect() {
   viewModel.onCorrect()
   updateScoreText()
   updateWordText()
}
  1. No GameFragment, atualize as variáveis score e word para usar as variáveis GameViewModel, porque elas agora estão no GameViewModel.
private fun updateWordText() {
   binding.wordText.text = viewModel.word
}

private fun updateScoreText() {
   binding.scoreText.text = viewModel.score.toString()
}
  1. No GameViewModel, no método nextWord(), remova as chamadas para os métodos updateWordText() e updateScoreText(). Esses métodos agora estão sendo chamados do GameFragment.
  2. Crie o app e verifique se não há erros. Se houver erros, limpe e recrie o projeto.
  3. 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.

  1. No GameFragment, adicione um método com o nome onEndGame(). O método onEndGame() será chamado quando o usuário tocar no botão Encerrar jogo.
private fun onEndGame() {
   }
  1. No GameFragment, no método onCreateView(), 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étodo onEndGame().
binding.endGameButton.setOnClickListener { onEndGame() }
  1. No GameFragment, adicione um método com o nome gameFinished() 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)
}
  1. No método onEndGame(), chame o método gameFinished().
private fun onEndGame() {
   gameFinished()
}
  1. 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.

  1. No pacote score, crie uma nova classe do Kotlin com o nome ScoreViewModel. Essa classe será a ViewModel do fragmento de pontuação.
  2. Estenda a classe ScoreViewModel da ViewModel.. Adicione um parâmetro construtor para a pontuação final. Adicione um bloco init com um log statement.
  3. Na classe ScoreViewModel, adicione uma variável chamada score 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")
   }
}
  1. No pacote score, crie outra classe do Kotlin com o nome ScoreViewModelFactory. Essa classe será responsável por instanciar o objeto ScoreViewModel.
  2. Estenda a classe ScoreViewModelFactory de ViewModelProvider.Factory. Adicione um parâmetro construtor para a pontuação final.
class ScoreViewModelFactory(private val finalScore: Int) : ViewModelProvider.Factory {
}
  1. Em ScoreViewModelFactory, o Android Studio mostra um erro sobre um membro abstrato não implementado. Para resolver o erro, modifique o método create(). No método create(), retorne o objeto ScoreViewModel 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")
}
  1. Em ScoreFragment, crie variáveis de classe para ScoreViewModel e ScoreViewModelFactory.
private lateinit var viewModel: ScoreViewModel
private lateinit var viewModelFactory: ScoreViewModelFactory
  1. No ScoreFragment, no método onCreateView(), depois de inicializar a variável binding, inicialize o viewModelFactory. Use a ScoreViewModelFactory. Transmita a pontuação final do pacote de argumentos como um parâmetro de construtor para o ScoreViewModelFactory().
viewModelFactory = ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(arguments!!).score)
  1. No onCreateView(, depois de inicializar o viewModelFactory, inicialize o objeto viewModel. Chame o método ViewModelProviders.of() e transmita o contexto do fragmento de pontuação associado e viewModelFactory. Isso criará o objeto ScoreViewModel usando o método de fábrica definido na classe viewModelFactory..
viewModel = ViewModelProviders.of(this, viewModelFactory)
       .get(ScoreViewModel::class.java)
  1. No método onCreateView(), depois de inicializar o viewModel, defina o texto da visualização scoreText como a pontuação final definida no ScoreViewModel.
binding.scoreText.text = viewModel.score.toString()
  1. 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.

  1. Opcional: verifique os registros ScoreViewModel no Logcat filtrando por ScoreViewModel. 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 o Fragment. 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 uma ViewModel.
  • A classe ViewModel armazena e gerencia dados relacionados à IU. A classe ViewModel 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 objeto ViewModel.

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 ScoreFragment que você criou neste codelab.

Um exemplo de ViewModel é a ScoreViewModel que você criou neste codelab.

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 ViewModel sim.

Contém uma referência ao ViewModel associado.

Não contém nenhuma referência ao controlador de IU associado.

Curso da Udacity:

Documentação do desenvolvedor Android:

Outro:

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: 5.2: LiveData e observadores 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.