Usar o LiveData com o ViewModel

Você aprendeu nos codelabs anteriores, como usar um ViewModel para armazenar os dados do app. O ViewModel permite que os dados do app sobrevivam às mudanças de configuração. Neste codelab, você aprenderá a integrar o LiveData com os dados no ViewModel.

A classe LiveData também faz parte dos Componentes da arquitetura do Android e é uma classe de armazenamento de dados que pode ser observada.

Pré-requisitos

  • Saber fazer o download do código-fonte do GitHub e abri-lo no Android Studio.
  • Saber criar e executar um app Android básico em Kotlin, usando atividades e fragmentos
  • Saber como os ciclos de vida da atividade e do fragmento funcionam.
  • Saber como armazenar dados da IU após mudanças da configuração do dispositivo usando um ViewModel.
  • Saber como escrever expressões lambda.

O que você aprenderá

  • Como usar LiveData e MutableLiveData no app.
  • Como encapsular os dados armazenados em um ViewModel com o LiveData.
  • Como adicionar métodos do observador para observar mudanças no LiveData.
  • Como escrever expressões de vinculação em um arquivo de layout.

O que você criará

  • Você usará LiveData para os dados do app (palavras, contagem de palavras e pontuação) no app Unscramble (link em inglês).
  • Adicionará métodos do observador que são notificados quando os dados mudam e atualizará a visualização de texto de palavras embaralhadas automaticamente.
  • Criará expressões de vinculação no arquivo de layout, que são acionadas quando o LiveData muda. A pontuação, a contagem de palavras e as visualizações de texto em palavras embaralhadas são atualizadas automaticamente.

O que é necessário

  • Um computador com o Android Studio instalado.
  • O código da solução do codelab anterior (o app Unscramble com ViewModel).

Fazer o download do código inicial para este codelab

Este codelab usa o app Unscramble criado por você no codelab anterior ( Armazenar dados no ViewModel) como o código inicial.

Este codelab usa o código de solução do Unscramble que você já viu no codelab anterior. O app exibirá uma palavra embaralhada para o jogador decifrar. O jogador pode tentar adivinhar a palavra correta quantas vezes quiser. Os dados do app, como a palavra atual, a pontuação e a contagem de palavras do jogador são salvos no ViewModel. No entanto, a IU do app não reflete a nova pontuação e os valores das contagens de palavras. Neste codelab, você implementará os recursos que faltam usando LiveData.

a20e6e45e0d5dc6f.png

LiveData é uma classe armazenadora de dados observáveis compatível com o ciclo de vida.

Algumas características do LiveData:

  • O LiveData armazena dados. O LiveData é um wrapper que pode ser usado com qualquer tipo de dados.
  • O LiveData é observável, o que significa que um observador é notificado quando os dados contidos no objeto LiveData mudam.
  • LiveData é compatível com o ciclo de vida. Quando você anexar um observador ao LiveData, ele estará associado a um LifecycleOwner (geralmente uma atividade ou fragmento). O LiveData só atualiza observadores que estão em um estado de ciclo de vida ativo, como STARTED (iniciado) ou RESUMED (retomado). Saiba mais sobre o LiveData e a observação neste link.

Atualização da IU no código inicial

No código inicial, o método updateNextWordOnScreen() é chamado explicitamente sempre que você quer exibir uma nova palavra embaralhada na IU. Esse método é chamado durante a inicialização do jogo e quando os jogadores pressionam o botão Submit (enviar) ou Skip (pular). Este método é chamado pelos métodos onViewCreated(), restartGame(), onSkipWord() e onSubmitWord(). Usando o Livedata, não será necessário chamar esse método em vários lugares para atualizar a IU. Isso será feito apenas uma vez no observador.

Nesta tarefa, você aprenderá a unir todos os dados com o LiveData,, convertendo a palavra atual no GameViewModel em LiveData. Em uma tarefa posterior, você adicionará um observador a esses objetos LiveData e aprenderá a observar o LiveData.

MutableLiveData

O MutableLiveData é a versão mutável do LiveData, ou seja, o valor dos dados armazenados dentro dele pode mudar.

  1. No GameViewModel, mude o tipo da variável _currentScrambledWord para MutableLiveData<String>. Os objetos LiveData e MutableLiveData são classes genéricas, então é necessário especificar o tipo de dados que eles contêm.
  2. Mude o tipo de variável _currentScrambledWord para val porque o valor do objeto LiveData/MutableLiveData permanecerá o mesmo, e somente os dados armazenados no objeto mudarão.
private val _currentScrambledWord = MutableLiveData<String>()
  1. Mude o tipo de campo de apoio currentScrambledWord para LiveData<String>, porque ele é imutável. O Android Studio mostrará alguns erros que você corrigirá nas próximas etapas.
val currentScrambledWord: LiveData<String>
   get() = _currentScrambledWord
  1. Para acessar os dados em um objeto LiveData, use a propriedade value. No método getNextWord() do GameViewModel, no bloco else, mude a referência de _currentScrambledWord para _currentScrambledWord.value.
private fun getNextWord() {
 ...
   } else {
       _currentScrambledWord.value = String(tempWord)
       ...
   }
}

Nesta tarefa, você configurará um observador no componente do app, GameFragment. O observador que você adicionará observará as mudanças dos dados currentScrambledWord do app. O LiveData é compatível com o ciclo de vida, o que significa que ele só atualiza os observadores que estão em um estado ativo do ciclo de vida. Assim, o observador no GameFragment só será notificado quando o GameFragment estiver nos estados STARTED ou RESUMED.

  1. No método GameFragment, exclua o método updateNextWordOnScreen() e todas as chamadas para ele. Este método não é necessário, porque você anexará um observador ao LiveData.
  2. No método onSubmitWord(), modifique o bloco vazio if-else da seguinte maneira. O método completo será semelhante a este.
private fun onSubmitWord() {
    val playerWord = binding.textInputEditText.text.toString()

    if (viewModel.isUserWordCorrect(playerWord)) {
        setErrorTextField(false)
        if (!viewModel.nextWord()) {
            showFinalScoreDialog()
        }
    } else {
        setErrorTextField(true)
    }
}
  1. Anexe um observador para o LiveData da currentScrambledWord. No GameFragment ao final do callback onViewCreated(), chame o método observe() em currentScrambledWord.
// Observe the currentScrambledWord LiveData.
viewModel.currentScrambledWord.observe()

O Android Studio exibirá um erro sobre parâmetros ausentes. Você corrigirá o erro na próxima etapa.

  1. Transmita viewLifecycleOwner como o primeiro parâmetro para o método observe(). O viewLifecycleOwner representa o ciclo de vida da visualização do fragmento. Esse parâmetro ajuda o LiveData a identificar o ciclo de vida do GameFragment e notificar o observador apenas quando o GameFragment estiver em estados ativos, como STARTED (iniciado) ou RESUMED (retomado).
  2. Adicione uma lambda como um segundo parâmetro usando newWord como um parâmetro de função. O newWord conterá o novo valor da palavra embaralhada.
// Observe the scrambledCharArray LiveData, passing in the LifecycleOwner and the observer.
viewModel.currentScrambledWord.observe(viewLifecycleOwner,
   { newWord ->
   })

Uma expressão lambda é uma função anônima que não é declarada, mas é transmitida imediatamente como uma expressão. Uma expressão lambda está sempre entre chaves { }.

  1. No corpo da função da expressão lambda, atribua a newWord à visualização de texto da palavra embaralhada.
// Observe the scrambledCharArray LiveData, passing in the LifecycleOwner and the observer.
viewModel.currentScrambledWord.observe(viewLifecycleOwner,
   { newWord ->
       binding.textViewUnscrambledWord.text = newWord
   })
  1. Compile e execute o app. Seu app de jogo funcionará exatamente como antes, mas agora a visualização de texto da palavra embaralhada é atualizada automaticamente no observador LiveData, não no método updateNextWordOnScreen().

Como na tarefa anterior, nesta tarefa, você adicionará o LiveData aos outros dados do app, à pontuação e à contagem de palavras para que a IU seja atualizada com os valores corretos da pontuação e da contagem de palavras durante o jogo.

Etapa 1: unir a pontuação e a contagem de palavras com o LiveData

  1. No GameViewModel, mude o tipo das variáveis de classe _score e _currentWordCount para val.
  2. Mude o tipo de dados das variáveis _score e _currentWordCount para MutableLiveData e inicialize-as como 0.
  3. Mude o tipo de campos de apoio para LiveData<Int>.
private val _score = MutableLiveData(0)
val score: LiveData<Int>
   get() = _score

private val _currentWordCount = MutableLiveData(0)
val currentWordCount: LiveData<Int>
   get() = _currentWordCount
  1. No GameViewModel no início do método reinitializeData(), mude a referência de _score e _currentWordCount para _score.value e _currentWordCount.value respectivamente.
fun reinitializeData() {
   _score.value = 0
   _currentWordCount.value = 0
   wordsList.clear()
   getNextWord()
}
  1. No GameViewModel, no método nextWord(), mude a referência de _currentWordCount para _currentWordCount.value!!.
fun nextWord(): Boolean {
    return if (_currentWordCount.value!! < MAX_NO_OF_WORDS) {
           getNextWord()
           true
       } else false
   }
  1. No GameViewModel, nos métodos increaseScore() e getNextWord(), mude a referência de _score e _currentWordCount para _score.value e _currentWordCount.value, respectivamente. O Android Studio mostrará um erro porque a _score não é mais um número inteiro, o tipo agora é LiveData. Você corrigirá esse erro nas próximas etapas.
  2. Use a função Kotlin plus() (link em inglês) para aumentar o valor de _score, que executa a adição com segurança de tipo nulo.
private fun increaseScore() {
    _score.value = (_score.value)?.plus(SCORE_INCREASE)
}
  1. Da mesma forma, use a função Kotlin inc() (link em inglês) para aumentar o valor em um com segurança de tipo nulo.
private fun getNextWord() {
   ...
    } else {
        _currentScrambledWord.value = String(tempWord)
        _currentWordCount.value = (_currentWordCount.value)?.inc()
        wordsList.add(currentWord)
       }
   }
  1. No GameFragment, acesse o valor da score usando a propriedade value. No método showFinalScoreDialog(), mude viewModel.score para viewModel.score.value.
private fun showFinalScoreDialog() {
   MaterialAlertDialogBuilder(requireContext())
       .setTitle(getString(R.string.congratulations))
       .setMessage(getString(R.string.you_scored, viewModel.score.value))
       ...
       .show()
}

Etapa 2: anexar observadores à pontuação e à contagem de palavras

No app, a pontuação e a contagem de palavras não são atualizadas. Você as atualizará nesta tarefa usando observadores LiveData.

  1. No GameFragment no método onViewCreated(), exclua o código que atualiza as visualizações de texto da pontuação e da contagem de palavras.

Remova:

binding.score.text = getString(R.string.score, 0)
binding.wordCount.text = getString(R.string.word_count, 0, MAX_NO_OF_WORDS)
  1. No GameFragment ao final do método onViewCreated(), anexe o observador para a score. Transmita o viewLifecycleOwner como o primeiro parâmetro para o observador e uma expressão lambda para o segundo parâmetro. Na expressão lambda, transmita a nova pontuação como um parâmetro e, dentro do corpo da função, defina a nova pontuação na visualização de texto.
viewModel.score.observe(viewLifecycleOwner,
   { newScore ->
       binding.score.text = getString(R.string.score, newScore)
   })
  1. No final do método onViewCreated(), anexe um observador para o LiveData da currentWordCount. Transmita o viewLifecycleOwner como o primeiro parâmetro para o observador e uma expressão lambda para o segundo parâmetro. Na expressão lambda, transmita a nova contagem de palavras como um parâmetro e, no corpo da função, defina a nova contagem de palavras usando MAX_NO_OF_WORDS para a visualização de texto.
viewModel.currentWordCount.observe(viewLifecycleOwner,
   { newWordCount ->
       binding.wordCount.text =
           getString(R.string.word_count, newWordCount, MAX_NO_OF_WORDS)
   })

Os novos observadores serão acionados quando o valor da pontuação e da contagem de palavras mudar no ViewModel, durante a vida útil do proprietário do ciclo de vida, ou seja, o GameFragment.

  1. Execute o app para ver o que acontece. Jogue com algumas palavras. A pontuação e a contagem de palavras também serão atualizadas corretamente na tela. Observe que você não está atualizando essas visualizações de texto com base em alguma condição no código. A score e a currentWordCount são LiveData, e os observadores correspondentes são chamados automaticamente quando o valor muda.

80e118245bdde6df.png

Nas tarefas anteriores, seu app detecta as mudanças de dados no código. Da mesma forma, os apps podem detectar mudanças nos dados do layout. Com a vinculação de dados, quando um valor LiveData observável muda, os elementos da IU no layout ao qual ele está vinculado também são notificados e a IU pode ser atualizada no layout.

Conceito: vinculação de dados

Nos codelabs anteriores, você viu a vinculação de visualizações, que é uma vinculação unidirecional. É possível vincular visualizações ao código, mas não vice-versa.

Resumo sobre a vinculação de visualizações:

A vinculação de visualizações é um recurso que facilita o acesso à visualizações no código. Ela gera uma classe de vinculação para cada arquivo do layout XML. A instância de uma classe de vinculação contém referências diretas a todas as visualizações que têm um ID no layout correspondente. Por exemplo, o app Unscramble usa a vinculação de visualizações atualmente para que as visualizações possam ser referenciadas no código usando a classe de vinculação gerada.

Exemplo:

binding.textViewUnscrambledWord.text = newWord
binding.score.text = getString(R.string.score, newScore)
binding.wordCount.text =
                  getString(R.string.word_count, newWordCount, MAX_NO_OF_WORDS)

Ao usar a vinculação de visualizações, não é possível referenciar os dados do app nas visualizações (arquivos de layout). Isso pode ser feito com a vinculação de dados.

Vinculação de dados

A biblioteca de vinculação de dados também faz parte da biblioteca do Android Jetpack. A vinculação de dados vincula os componentes da IU nos seus layouts a fontes de dados do app usando um formato declarativo, que você aprenderá mais tarde no codelab.

Em termos mais simples, esse recurso vincula dados (do código) a visualizações além de vincular visualizações (ao código).

Exemplo de uso da vinculação de visualizações no controlador da IU

binding.textViewUnscrambledWord.text = viewModel.currentScrambledWord

Exemplo de uso da vinculação de dados no arquivo de layout

android:text="@{gameViewModel.currentScrambledWord}"

O exemplo acima mostra como usar a biblioteca Data Binding para atribuir dados do app às visualizações/widget diretamente no arquivo de layout. A sintaxe @{} é usada na expressão de atribuição.

A principal vantagem de usar a vinculação de dados é que ela permite remover muitas chamadas de framework da IU nas suas atividades, tornando-as mais simples e de fácil manutenção. Isso também pode melhorar o desempenho do app e ajudar a evitar vazamentos de memória e exceções de ponteiro nulo.

Etapa 1: substituir a vinculação de visualizações pela vinculação de dados

  1. No arquivo build.gradle(Module), ative a propriedade dataBinding na seção buildFeatures.

Substitua

buildFeatures {
   viewBinding = true
}

por

buildFeatures {
   dataBinding = true
}

Faça uma sincronização do Gradle quando solicitado pelo Android Studio.

  1. Para usar a vinculação de dados em qualquer projeto do Kotlin, use o plug-in kotlin-kapt. Esta etapa já foi feita para você no arquivo build.gradle(Module).
plugins {
   id 'com.android.application'
   id 'kotlin-android'
   id 'kotlin-kapt'
}

As etapas acima geram automaticamente uma classe de vinculação para cada arquivo XML do layout no app. Se o nome do arquivo de layout for activity_main.xml, sua classe de geração automática terá o nome ActivityMainBinding.

Etapa 2: converter o arquivo de layout em um layout de vinculação de dados

Os arquivos de layout de vinculação de dados são relativamente diferentes e começam com uma tag raiz de <layout>, seguida de um elemento <data> opcional e um elemento raiz view. Esse elemento de visualização é o que a raiz deveria ser em um arquivo de layout sem vinculação.

  1. Abra o arquivo game_fragment.xml e selecione a guia Code.
  2. Para converter o layout para um de vinculação de dados, coloque o elemento raiz em uma tag <layout>. Também será necessário mover as definições do namespace (os atributos que começam com xmlns:) para o novo elemento raiz. Adicione tags <data></data> dentro da tag <layout> acima do elemento raiz. O Android Studio oferece uma maneira prática de fazer isso automaticamente: clique com o botão direito do mouse no elemento raiz (ScrollView) e selecione Show Context Actions > Convert to data binding layout.

f356fc45e8fe91b1.png

  1. O layout ficará semelhante a este:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools">

   <data>

   </data>

   <ScrollView
       android:layout_width="match_parent"
       android:layout_height="match_parent">

       <androidx.constraintlayout.widget.ConstraintLayout
         ...
       </androidx.constraintlayout.widget.ConstraintLayout>
   </ScrollView>
</layout>
  1. No GameFragment, no início do método onCreateView(), mude a instanciação da variável binding para usar a vinculação de dados.

Substitua

binding = GameFragmentBinding.inflate(inflater, container, false)

Por

binding = DataBindingUtil.inflate(inflater, R.layout.game_fragment, container, false)
  1. Compile o código. Ele será compilado sem problemas. Agora o app usa a vinculação de dados, e as visualizações do layout podem acessar os dados do app.

Nesta tarefa, você adicionará propriedades ao arquivo do layout para acessar os dados do app do viewModel. Você inicializará as variáveis do layout no código.

  1. Em game_fragment.xml, dentro da tag <data>, adicione uma tag filha com o nome <variable>, declare uma propriedade com o nome gameViewModel e o tipo GameViewModel. Ela será usada para vincular os dados do ViewModel ao layout.
<data>
   <variable
       name="gameViewModel"
       type="com.example.android.unscramble.ui.game.GameViewModel" />
</data>

O tipo gameViewModel contém o nome do pacote. Verifique se o nome do pacote corresponde ao nome do pacote do seu app.

  1. Abaixo da declaração gameViewModel, adicione outra variável dentro da tag <data> do tipo Integer e nomeie-a como maxNoOfWords. Ela será usada para se vincular à variável no ViewModel para armazenar o número de palavras por jogo.
<data>
   ...
   <variable
       name="maxNoOfWords"
       type="int" />
</data>
  1. No GameFragment no início do método onViewCreated(), inicialize as variáveis de layout gameViewModel e maxNoOfWords.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
   super.onViewCreated(view, savedInstanceState)

   binding.gameViewModel = viewModel

   binding.maxNoOfWords = MAX_NO_OF_WORDS
...
}
  1. O LiveData é observável e compatível com o ciclo de vida, então você precisa transmitir o proprietário do ciclo de vida ao layout. No GameFragment, no método onViewCreated(), abaixo da inicialização das variáveis de vinculação, adicione o seguinte código.
   // Specify the fragment view as the lifecycle owner of the binding.
   // This is used so that the binding can observe LiveData updates
   binding.lifecycleOwner = viewLifecycleOwner

Lembre-se de que você implementou uma funcionalidade semelhante ao implementar os observadores LiveData. Você transmitiu viewLifecycleOwner como um dos parâmetros para os observadores LiveData.

As expressões de vinculação são escritas no layout das propriedades do atributo (como android:text) e fazem referência às propriedades do layout. As propriedades do layout são declaradas na parte superior do arquivo de layout de vinculação de dados pela tag <variable>. Quando qualquer uma das variáveis dependentes muda, a biblioteca Data Binding executará suas expressões de vinculação (e atualizará as visualizações). Essa detecção de mudança é uma ótima otimização que a biblioteca Data Binding oferece gratuitamente.

Sintaxe para expressões de vinculação

As expressões de vinculação começam com um símbolo @ e ficam entre chaves {}. No exemplo a seguir, o texto TextView é definido como a propriedade firstName da variável user:

Exemplo:

<TextView android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@{user.firstName}" />

Etapa 1: adicionar uma expressão de vinculação à palavra atual

Nesta etapa, vincule a visualização de texto da palavra atual ao objeto LiveData no ViewModel.

  1. No arquivo game_fragment.xml, adicione um atributo text à visualização de texto textView_unscrambled_word. Use a nova variável de layout, gameViewModel, e atribua @{gameViewModel.currentScrambledWord} ao atributo text.
<TextView
   android:id="@+id/textView_unscrambled_word"
   ...
   android:text="@{gameViewModel.currentScrambledWord}"
   .../>
  1. Em GameFragment, remova o código do observador LiveData para a currentScrambledWord. Esse código do observador não é mais necessário no fragmento. O layout recebe as atualizações das mudanças diretamente no LiveData.

Remova

viewModel.currentScrambledWord.observe(viewLifecycleOwner,
   { newWord ->
       binding.textViewUnscrambledWord.text = newWord
   })
  1. Execute o app, ele funcionará como antes. Mas agora, a visualização de texto com palavras embaralhadas usa as expressões de vinculação para atualizar a IU, não os observadores LiveData.

Etapa 2: adicionar uma expressão de vinculação à pontuação e à contagem de palavras

Recursos em expressões de vinculação de dados

Uma expressão de vinculação de dados pode referenciar recursos do app usando esta sintaxe.

Exemplo:

android:padding="@{@dimen/largePadding}"

No exemplo acima, o arquivo de recursos dimen.xml atribui um valor de largePadding ao atributo padding.

Também é possível transmitir as propriedades do layout como parâmetros de recursos.

Exemplo:

android:text="@{@string/example_resource(user.lastName)}"

strings.xml

<string name="example_resource">Last Name: %s</string>

No exemplo acima, example_resource é um recurso de string com o marcador %s. Você transmite user.lastName como um parâmetro de recurso na expressão de vinculação, em que user é uma variável de layout.

Nesta etapa, você adicionará expressões de vinculação às visualizações de texto de pontuação e de contagem de palavras, transmitindo os parâmetros do recurso. Esta etapa é semelhante ao que você fez na textView_unscrambled_word acima.

  1. No arquivo game_fragment.xml, atualize o atributo text para a visualização de texto word_count com esta expressão de vinculação. Use o recurso de string word_count e transmita gameViewModel.currentWordCount e maxNoOfWords como parâmetros de recurso.
<TextView
   android:id="@+id/word_count"
   ...
   android:text="@{@string/word_count(gameViewModel.currentWordCount, maxNoOfWords)}"
   .../>
  1. Atualize o atributo text da visualização de texto da score com a seguinte expressão de vinculação. Use o recurso de string score e transmita gameViewModel.score como um parâmetro de recurso.
<TextView
   android:id="@+id/score"
   ...
   android:text="@{@string/score(gameViewModel.score)}"
   ... />
  1. Remova os observadores LiveData do GameFragment. Eles não são mais necessários, as expressões de vinculação atualizam a IU quando os objetos LiveData correspondentes mudam.

Remova:

viewModel.score.observe(viewLifecycleOwner,
   { newScore ->
       binding.score.text = getString(R.string.score, newScore)
   })

viewModel.currentWordCount.observe(viewLifecycleOwner,
   { newWordCount ->
       binding.wordCount.text =
           getString(R.string.word_count, newWordCount, MAX_NO_OF_WORDS)
   })
  1. Execute o app e jogue com algumas palavras. Agora, seu código usa o LiveData e expressões de vinculação para atualizar a IU.

7880e60dc0a6f95c.png 9ef2fdf21ffa5c99.png

Parabéns! Você aprendeu a usar o LiveData com observadores LiveData e o LiveData com expressões de vinculação.

Conforme você aprendeu neste curso, o ideal é criar apps acessíveis ao maior número possível de usuários. Alguns usuários podem usar o Talkback para acessar e navegar pelo app. O TalkBack é o leitor de tela do Google incluso em dispositivos Android. Ele oferece feedback falado para que você possa usar seu dispositivo sem olhar para a tela.

Com o Talkback ativado, verifique se o jogo funciona.

  1. Ative o Talkback no seu dispositivo seguindo estas instruções.
  2. Retorne ao app Unscramble.
  3. Navegue pelo app usando o Talkback seguindo estas instruções. Deslize para a direita para navegar pelos elementos da tela em sequência e deslize para a esquerda para ir na direção oposta. Toque duas vezes em qualquer lugar para selecionar. Confirme se você pode acessar todos os elementos do app com gestos de deslizar.
  4. Verifique se o usuário do Talkback consegue navegar por todos os ítens da tela.
  5. O Talkback tentará ler a palavra embaralhada como uma palavra normal. Isso pode ser confuso para o jogador, já que ela não é uma palavra real.
  6. Uma melhor experiência do usuário seria fazer o Talkback ler em voz alta os caracteres individuais da palavra embaralhada. No GameViewModel, converta a String da palavra embaralhada para uma string Spannable. Uma string "spannable" é uma string com algumas informações extras anexadas a ela. Neste caso, queremos associar a string com um TtsSpan de TYPE_VERBATIM, para que o mecanismo de conversão de texto em voz leia em voz alta a palavra embaralhada caractere por caractere.
  7. No GameViewModel,, use este código para modificar como a variável currentScrambledWord é declarada:
val currentScrambledWord: LiveData<Spannable> = Transformations.map(_currentScrambledWord) {
    if (it == null) {
        SpannableString("")
    } else {
        val scrambledWord = it.toString()
        val spannable: Spannable = SpannableString(scrambledWord)
        spannable.setSpan(
            TtsSpan.VerbatimBuilder(scrambledWord).build(),
            0,
            scrambledWord.length,
            Spannable.SPAN_INCLUSIVE_INCLUSIVE
        )
        spannable
    }
}

Essa variável agora tem o tipo LiveData<Spannable> em vez de LiveData<String>. Não é necessário entender todos os detalhes de como isso funciona, mas a implementação usa uma transformação LiveData para converter a String da palavra embaralhada atual em uma string spannable que pode ser tratada de maneira adequada pelo serviço de acessibilidade. No próximo codelab, você aprenderá mais sobre transformações do LiveData, que permitem retornar uma instância do LiveData diferente com base no valor correspondente do LiveData.

  1. Execute o app Unscramble e navegue por ele com o Talkback. O TalkBack lerá os caracteres individuais da palavra embaralhada agora.

Para ver mais informações sobre como tornar seu app mais acessível, consulte estes princípios.

É recomendável excluir o código inativo, não utilizado e indesejado do código da solução. Isso facilitará a manutenção do código, o que também facilita a compreensão dele por novos colegas de equipe.

  1. No GameFragment, exclua os métodos getNextScrambledWord() e onDetach().
  2. No GameViewModel, exclua o método onCleared().
  3. Exclua todas as importações não utilizadas na parte superior dos arquivos de origem. Elas ficarão esmaecidas.

Você não precisa mais das declarações de registro. Se quiser, você pode excluí-las do código.

  1. [Opcional] Exclua as instruções Log nos arquivos de origem (GameFragment.kt e GameViewModel.kt) adicionados no codelab anterior para entender o ciclo de vida do ViewModel.

O código da solução para este codelab está no projeto mostrado abaixo.

Para encontrar o código deste codelab e abri-lo no Android Studio, faça o seguinte.

Buscar o código

  1. Clique no URL fornecido. Isso abrirá a página do GitHub referente ao projeto em um navegador.
  2. Na página do GitHub do projeto, clique no botão Code, que exibirá uma caixa de diálogo.

5b0a76c50478a73f.png

  1. Na caixa de diálogo, clique no botão Download ZIP para salvar o projeto no seu computador. Aguarde a conclusão do download.
  2. Localize o arquivo no computador, que provavelmente está na pasta Downloads.
  3. Clique duas vezes no arquivo ZIP para descompactá-lo. Isso criará uma nova pasta com os arquivos do projeto.

Abrir o projeto no Android Studio

  1. Inicie o Android Studio.
  2. Na janela Welcome to Android Studio, clique em Open an existing Android Studio project.

36cc44fcf0f89a1d.png

Observação: caso o Android Studio já esteja aberto, selecione a opção File > New > Import Project.

21f3eec988dcfbe9.png

  1. Na caixa de diálogo Import Project, vá até a pasta do projeto descompactada, que provavelmente está na pasta Downloads.
  2. Clique duas vezes nessa pasta do projeto.
  3. Aguarde o Android Studio abrir o projeto.
  4. Clique no botão Run 11c34fc5e516fb1c.png para criar e executar o app. Confira se ele é compilado da forma esperada.
  5. Procure os arquivos do projeto na janela de ferramentas Project para ver como o app está configurado.
  • O LiveData armazena dados. O LiveData é um wrapper que pode ser usado com qualquer tipo de dados.
  • O LiveData é observável, o que significa que um observador é notificado quando os dados contidos no objeto LiveData mudam.
  • LiveData é compatível com o ciclo de vida. Quando você anexar um observador ao LiveData, ele estará associado a um LifecycleOwner (geralmente uma atividade ou fragmento). O LiveData só atualiza observadores que estão em um estado de ciclo de vida ativo, como STARTED ou RESUMED. Saiba mais sobre o LiveData e a observação neste link.
  • Os apps podem detectar mudanças do LiveData no layout usando a vinculação de dados e expressões de vinculação.
  • As expressões de vinculação são escritas no layout das propriedades do atributo (como android:text) e fazem referência às propriedades do layout.

Postagens do blog