Cómo usar LiveData con ViewModel

En los codelabs anteriores, aprendiste a usar un ViewModel para almacenar los datos de la app. ViewModel permite que los datos de la app sobrevivan a los cambios de configuración. En este codelab, aprenderás a integrar LiveData con los datos de ViewModel.

La clase LiveData también forma parte de los componentes de la arquitectura de Android y es una clase de retención de datos que puede observarse.

Requisitos previos

  • Cómo descargar el código fuente de GitHub y abrirlo en Android Studio
  • Cómo crear y ejecutar una app básica de Android en Kotlin mediante actividades y fragmentos
  • Cómo funcionan los ciclos de vida de actividades y fragmentos
  • Cómo retener datos de la IU mediante cambios en la configuración de dispositivos mediante un ViewModel
  • Cómo escribir expresiones lambda

Qué aprenderás

  • Cómo usar LiveData y MutableLiveData en tu app
  • Cómo encapsular los datos almacenados en una ViewModel con LiveData
  • Cómo agregar métodos de observador para observar cambios en LiveData.
  • Cómo escribir expresiones de vinculación en un archivo de diseño

Qué compilarás

  • Usa LiveData para los datos de la app (texto, recuento de palabras y puntuación) en la app de Unscramble.
  • Agrega métodos de observador que reciben una notificación cuando cambian los datos y actualiza la vista de texto de la palabra desordenada automáticamente.
  • Escribe expresiones de vinculación en el archivo de diseño, que se activan cuando se cambia el LiveData subyacente. La puntuación, el recuento de palabras y las vistas de texto de palabras codificadas se actualizan automáticamente.

Requisitos

  • Una computadora que tenga Android Studio instalado
  • Código de solución del codelab anterior (app de Unscramble con ViewModel)

Cómo descargar el código de inicio para este codelab

En este codelab, se usa la app Unscramble que creaste en el codelab anterior (Cómo almacenar datos en ViewModel) como código inicial.

En este codelab, se usa el código de solución de Unscramble que conoces del codelab anterior. La app muestra una palabra desordenada para que el jugador la ordene. El jugador puede intentar varias veces para adivinar la palabra correcta. Los datos de la app, como la palabra actual, la puntuación del jugador y el recuento de palabras, se guardan en ViewModel. Sin embargo, la IU de la app no refleja los nuevos valores de puntuación y recuento de palabras. En este codelab, implementarás las funciones que faltan con LiveData.

a20e6e45e0d5dc6f.png

LiveData es una clase de retención de datos observable que tiene en cuenta el ciclo de vida.

Estas son algunas características de LiveData:

  • LiveData contiene datos; LiveData es un wrapper que se puede usar con cualquier tipo de datos.
  • LiveData es observable, lo que significa que se notifica a un observador cuando cambian los datos retenidos por el objeto LiveData.
  • LiveData está optimizado para los ciclos de vida. Cuando adjuntas un observador a LiveData, el observador se asocia con un LifecycleOwner (por lo general, una actividad o un fragmento). El LiveData solamente actualiza los observadores que tienen un estado de ciclo de vida activo, como STARTED o RESUMED. Puedes obtener más información sobre LiveData y la observación aquí.

Actualización de la IU en el código de inicio

En el código de inicio, se llama de manera explícita al método updateNextWordOnScreen(), cada vez que quieres mostrar una nueva palabra desordenada en la IU. Debes llamar a este método durante la inicialización del juego y cuando los jugadores presionen el botón Submit o Skip. Se llama a este método desde los métodos onViewCreated(), restartGame(), onSkipWord() y onSubmitWord(). Con Livedata, no tendrás que llamar a este método desde varios lugares para actualizar la IU. Lo harás una sola vez en el observador.

En esta tarea, aprenderás a unir datos con LiveData, mediante la conversión de la palabra actual en GameViewModel a LiveData. En una tarea posterior, agregarás un observador a estos objetos LiveData y aprenderás a observar LiveData.

MutableLiveData

MutableLiveData es la versión mutable de LiveData, es decir, el valor de los datos almacenados dentro de este se puede cambiar.

  1. En GameViewModel, cambia el tipo de la variable _currentScrambledWord a MutableLiveData<String>. LiveData y MutableLiveData son clases genéricas, por lo que debes especificar el tipo de datos que retienen.
  2. Cambia el tipo de variable de _currentScrambledWord a val porque el valor del objeto LiveData/MutableLiveData se mantendrá igual, y solo cambiarán los datos almacenados en el objeto.
private val _currentScrambledWord = MutableLiveData<String>()
  1. Cambia el tipo del campo de copia de seguridad currentScrambledWord a LiveData<String>, ya que es inmutable. Android Studio mostrará algunos errores que corregirás en los próximos pasos.
val currentScrambledWord: LiveData<String>
   get() = _currentScrambledWord
  1. Para acceder a los datos de un objeto LiveData, usa la propiedad value. En GameViewModel dentro del método getNextWord(), en el bloque else, cambia la referencia de _currentScrambledWord a _currentScrambledWord.value.
private fun getNextWord() {
 ...
   } else {
       _currentScrambledWord.value = String(tempWord)
       ...
   }
}

En esta tarea, configurarás un observador en el componente de aplicación, GameFragment. El observador que agregarás observa los cambios en los datos de la app currentScrambledWord. LiveData está optimizado para los ciclos de vida, lo que significa que solo actualiza observadores que están en estado de ciclo de vida activo. Por lo tanto, el observador de GameFragment solamente recibirá una notificación cuando GameFragment tenga el estado STARTED o RESUMED.

  1. En GameFragment, borra el método updateNextWordOnScreen() y todas las llamadas a él. No necesitas este método, ya que adjuntarás un observador a LiveData.
  2. En onSubmitWord(), modifica el bloque if-else vacío de la siguiente manera. El método completo debe verse así.
private fun onSubmitWord() {
    val playerWord = binding.textInputEditText.text.toString()

    if (viewModel.isUserWordCorrect(playerWord)) {
        setErrorTextField(false)
        if (!viewModel.nextWord()) {
            showFinalScoreDialog()
        }
    } else {
        setErrorTextField(true)
    }
}
  1. Adjunta un observador de currentScrambledWord LiveData. En GameFragment al final de la devolución de llamada onViewCreated(), llama al método observe() en currentScrambledWord.
// Observe the currentScrambledWord LiveData.
viewModel.currentScrambledWord.observe()

Android Studio mostrará un error sobre los parámetros faltantes. Solucionarás el error en el siguiente paso.

  1. Pasa viewLifecycleOwner como el primer parámetro al método observe(). El objeto viewLifecycleOwner representa el ciclo de vida de vista del fragmento. Este parámetro ayuda a que LiveData conozca el ciclo de vida de GameFragment y notifique al observador solamente cuando GameFragment tenga estados activos (STARTED o RESUMED).
  2. Agrega una lambda como segundo parámetro con newWord como parámetro de función. newWord contendrá el valor de la nueva palabra desordenada.
// Observe the scrambledCharArray LiveData, passing in the LifecycleOwner and the observer.
viewModel.currentScrambledWord.observe(viewLifecycleOwner,
   { newWord ->
   })

Una expresión lambda es una función anónima que no se declara, sino que se pasa inmediatamente como una expresión. Una expresión lambda siempre está rodeada de llaves { }.

  1. En el cuerpo de la función de la expresión lambda, asigna newWord a la vista de texto de la palabra desordenada.
// Observe the scrambledCharArray LiveData, passing in the LifecycleOwner and the observer.
viewModel.currentScrambledWord.observe(viewLifecycleOwner,
   { newWord ->
       binding.textViewUnscrambledWord.text = newWord
   })
  1. Compila y ejecuta la app. Tu app de juego debería funcionar exactamente como antes, pero ahora la vista de texto de la palabra desordenada se actualiza automáticamente en el observador LiveData, no en el método updateNextWordOnScreen().

Como en la tarea anterior, en esta tarea agregarás LiveData a los demás datos en la app, la puntuación y el recuento de palabras, de modo que la IU se actualice con los valores correctos de la puntuación y el recuento de palabras durante el juego.

Paso 1: Puntuación y recuento de palabras con LiveData

  1. En GameViewModel, cambia el tipo de las variables de clase _score y _currentWordCount a val.
  2. Cambia el tipo de datos de las variables _score y _currentWordCount a MutableLiveData e inicialízalos a 0.
  3. Cambia el tipo de campo de copia de seguridad a 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. En GameViewModel al principio del método reinitializeData(), cambia la referencia de _score y _currentWordCount a _score.value y _currentWordCount.value, respectivamente.
fun reinitializeData() {
   _score.value = 0
   _currentWordCount.value = 0
   wordsList.clear()
   getNextWord()
}
  1. En GameViewModel, dentro del método nextWord(), cambia la referencia de _currentWordCount a _currentWordCount.value!!.
fun nextWord(): Boolean {
    return if (_currentWordCount.value!! < MAX_NO_OF_WORDS) {
           getNextWord()
           true
       } else false
   }
  1. En GameViewModel, dentro de los métodos increaseScore() y getNextWord(), cambia la referencia de _score y _currentWordCount a _score.value y _currentWordCount.value, respectivamente. Android Studio te mostrará un error porque _score ya no es un número entero, sino que es LiveData. Lo corregirás en los pasos siguientes.
  2. Usa la función de Kotlin plus() para aumentar el valor de _score, que realiza la adición con seguridad nula.
private fun increaseScore() {
    _score.value = (_score.value)?.plus(SCORE_INCREASE)
}
  1. Del mismo modo, usa la función de Kotlin inc() para aumentar el valor de a uno con seguridad nula.
private fun getNextWord() {
   ...
    } else {
        _currentScrambledWord.value = String(tempWord)
        _currentWordCount.value = (_currentWordCount.value)?.inc()
        wordsList.add(currentWord)
       }
   }
  1. En GameFragment, accede al valor de score con la propiedad value. Dentro del método showFinalScoreDialog(), cambia viewModel.score a viewModel.score.value.
private fun showFinalScoreDialog() {
   MaterialAlertDialogBuilder(requireContext())
       .setTitle(getString(R.string.congratulations))
       .setMessage(getString(R.string.you_scored, viewModel.score.value))
       ...
       .show()
}

Paso 2: Adjunta observadores a la puntuación y al recuento de palabras

En la app, no se actualizan la puntuación ni el recuento de palabras. En esta tarea, los actualizarás con observadores LiveData.

  1. En GameFragment dentro del método onViewCreated(), borra el código que actualiza las vistas de texto de puntuación y recuento de palabras.

Quita:

binding.score.text = getString(R.string.score, 0)
binding.wordCount.text = getString(R.string.word_count, 0, MAX_NO_OF_WORDS)
  1. En GameFragment, al final del método onViewCreated(), adjunta un observador de score. Pasa viewLifecycleOwner como el primer parámetro al observador y una expresión lambda para el segundo parámetro. Dentro de la expresión lambda, pasa la nueva puntuación como parámetro y dentro del cuerpo de la función, establece la nueva puntuación en la vista de texto.
viewModel.score.observe(viewLifecycleOwner,
   { newScore ->
       binding.score.text = getString(R.string.score, newScore)
   })
  1. Al final del método onViewCreated(), adjunta un observador para currentWordCount LiveData. Pasa viewLifecycleOwner como el primer parámetro al observador y una expresión lambda para el segundo parámetro. Dentro de la expresión lambda, pasa el nuevo recuento de palabras como parámetro y, en el cuerpo de la función, establece el nuevo recuento de palabras junto con el MAX_NO_OF_WORDS en la vista de texto.
viewModel.currentWordCount.observe(viewLifecycleOwner,
   { newWordCount ->
       binding.wordCount.text =
           getString(R.string.word_count, newWordCount, MAX_NO_OF_WORDS)
   })

Los nuevos observadores se activarán cuando el valor de la puntuación y el recuento de palabras cambien en ViewModel, durante la vida útil del propietario del ciclo de vida, es decir, el GameFragment.

  1. Ejecuta la app para ver los cambios. Prueba el juego con algunas palabras. La puntuación y el recuento de palabras también se actualizan correctamente en la pantalla. Observa que no actualizas estas vistas de texto según algunas condiciones del código. score y currentWordCount son LiveData, y se llama automáticamente a los observadores correspondientes cuando cambia el valor subyacente.

80e118245bdde6df.png

En las tareas anteriores, tu app escucha los cambios en los datos del código. Del mismo modo, las apps pueden escuchar los cambios de datos desde el diseño. Con la vinculación de datos, cuando cambia un valor observable LiveData, los elementos de la IU en el diseño al que está vinculado también reciben una notificación y la IU se puede actualizar desde el diseño.

Concepto: Vinculación de datos

En los codelabs anteriores, viste la vinculación de vistas, que es una vinculación unidireccional. Puedes vincular vistas al código, pero no al revés.

Actualizador para la vinculación de vistas:

La vinculación de vistas es una función que te permite acceder más fácilmente a las vistas de código. Genera una clase de vinculación para cada archivo de diseño XML. Una instancia de una clase de vinculación contiene referencias directas a todas las vistas que tienen un ID en el diseño correspondiente. Por ejemplo, la app de Unscramble actualmente usa la vinculación de vistas, de modo que se puede hacer referencia a las vistas en el código mediante la clase de vinculación generada.

Ejemplo:

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)

Con la vinculación de vista, no puedes hacer referencia a los datos de la app en las vistas (archivos de diseño). Esto se puede lograr con la vinculación de datos.

Vinculación de datos

La biblioteca de vinculación de datos también forma parte de la biblioteca de Android Jetpack. La vinculación de datos vincula los componentes de IU de tus diseños con las fuentes de datos de tu app usando un formato declarativo, que aprenderás más adelante en el codelab.

En términos más simples, la vinculación de datos asocia datos (del código) con las vistas y la vinculación de vistas (vinculación de vistas con el código).

Ejemplo con vinculación de vistas en el controlador de IU

binding.textViewUnscrambledWord.text = viewModel.currentScrambledWord

Ejemplo con vinculación de datos en un archivo de diseño

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

El ejemplo anterior muestra cómo usar la biblioteca de vinculación de datos para asignar datos de app a las vistas o al widget directamente en el archivo de diseño. Observa el uso de la sintaxis @{} en la expresión de asignación:

La principal ventaja de usar la vinculación de datos es que permite quitar varias llamadas al framework de la IU en las actividades, que resultan más sencillas y fáciles de mantener. Esto también puede mejorar el rendimiento de la app y ayudar a evitar pérdidas de memoria y excepciones de puntero nulo.

Paso 1: Cambia la vinculación de vista a la vinculación de datos

  1. En el archivo build.gradle(Module), habilita la propiedad dataBinding en la sección buildFeatures.

Reemplaza

buildFeatures {
   viewBinding = true
}

Con

buildFeatures {
   dataBinding = true
}

Realiza una sincronización de Gradle cuando Android Studio lo solicite

  1. Para usar la vinculación de datos en cualquier proyecto de Kotlin, debes aplicar el complemento kotlin-kapt. Este paso ya está realizado en el archivo build.gradle(Module).
plugins {
   id 'com.android.application'
   id 'kotlin-android'
   id 'kotlin-kapt'
}

Los pasos anteriores generan automáticamente una clase de vinculación para cada archivo XML de diseño en la app. Si el nombre del archivo de diseño es activity_main.xml, entonces tu clase de autogen se llamará ActivityMainBinding.

Paso 2: Convierte el archivo de diseño en un diseño de vinculación de datos

Los archivos de diseño de la vinculación de datos son ligeramente diferentes y comienzan con una etiqueta raíz de <layout> seguida de un elemento opcional de <data> y un elemento raíz de view. Este elemento de vista corresponde al elemento raíz en un archivo de diseño no vinculante.

  1. Abre game_fragment.xml y selecciona la pestaña code.
  2. Para convertir el diseño en un diseño de vinculación de datos, une el elemento raíz en una etiqueta <layout>. También deberás mover las definiciones de espacio de nombres (los atributos que comienzan con xmlns:) al nuevo elemento raíz. Agrega etiquetas <data></data> dentro de la etiqueta <layout> encima del elemento raíz. Android Studio ofrece una manera práctica de hacerlo automáticamente: haz clic con el botón derecho en el elemento raíz (ScrollView) y selecciona Show Context Actions > Convert to data binding layout.

f356fc45e8fe91b1.png

  1. El diseño debería tener el siguiente aspecto:
<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. En GameFragment, al principio del método onCreateView(), cambia la creación de instancias de la variable binding para usar la vinculación de datos.

Reemplaza

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

Con

binding = DataBindingUtil.inflate(inflater, R.layout.game_fragment, container, false)
  1. Compila el código. Deberías poder hacerlo sin problemas. Ahora tu app usa la vinculación de datos y las vistas en el diseño pueden acceder a los datos de esta.

En esta tarea, agregarás propiedades en el archivo de diseño para acceder a los datos de la app de viewModel. Debes inicializar las variables de diseño en el código.

  1. En game_fragment.xml, dentro de la etiqueta <data> y una etiqueta secundaria llamada <variable>, declara una propiedad con el nombre gameViewModel y del tipo GameViewModel. Usarás esto para vincular los datos en ViewModel al diseño.
<data>
   <variable
       name="gameViewModel"
       type="com.example.android.unscramble.ui.game.GameViewModel" />
</data>

Observa que el tipo de gameViewModel contiene el nombre del paquete. Asegúrate de que el nombre de este paquete coincida con el nombre del paquete en tu app.

  1. Debajo de la declaración gameViewModel, agrega otra variable dentro de la etiqueta <data> del tipo Integer y asígnale el nombre maxNoOfWords. Usarás esto para vincular a la variable en ViewModel para almacenar la cantidad de palabras por juego.
<data>
   ...
   <variable
       name="maxNoOfWords"
       type="int" />
</data>
  1. En GameFragment al comienzo del método onViewCreated(), inicializa las variables de diseño gameViewModel y maxNoOfWords.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
   super.onViewCreated(view, savedInstanceState)

   binding.gameViewModel = viewModel

   binding.maxNoOfWords = MAX_NO_OF_WORDS
...
}
  1. LiveData es observable y está optimizada para ciclos de vida, por lo que debes pasar el propietario del ciclo de vida al diseño. En GameFragment, dentro del método onViewCreated(), debajo de la inicialización de las variables de vinculación, agrega el siguiente 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

Recuerda que implementaste una funcionalidad similar cuando implementes observadores LiveData. Pasaste viewLifecycleOwner como uno de los parámetros a los observadores LiveData.

Las expresiones de vinculación se escriben dentro del diseño en las propiedades del atributo (como android:text) que hacen referencia a las propiedades del diseño. Las propiedades de diseño se declaran en la parte superior del archivo de diseño de vinculación de datos, con la etiqueta <variable>. Cuando cambien cualquiera de las variables dependientes, la "Biblioteca de DB" ejecutará tus expresiones de vinculación (y, por lo tanto, actualizará las vistas). Esta detección de cambios es una excelente optimización que se obtiene de forma gratuita cuando se usa una biblioteca de vinculación de datos.

Sintaxis para expresiones de vinculación

Las expresiones de vinculación comienzan con un símbolo @ y se envuelven entre llaves {}. En el siguiente ejemplo, el texto TextView se configura como la propiedad firstName de la variable user:

Ejemplo:

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

Paso 1: Agrega la expresión de vinculación a la palabra actual

En este paso, vinculas la vista de texto de la palabra actual al objeto LiveData en ViewModel.

  1. En game_fragment.xml, agrega un atributo text a la vista de texto textView_unscrambled_word. Usa la nueva variable de diseño, gameViewModel, y asigna @{gameViewModel.currentScrambledWord} al atributo text.
<TextView
   android:id="@+id/textView_unscrambled_word"
   ...
   android:text="@{gameViewModel.currentScrambledWord}"
   .../>
  1. En GameFragment, quita el código del observador LiveData para currentScrambledWord: ya no necesitas el código del observador en el fragmento. El diseño recibe las actualizaciones de los cambios en LiveData directamente.

Quita

viewModel.currentScrambledWord.observe(viewLifecycleOwner,
   { newWord ->
       binding.textViewUnscrambledWord.text = newWord
   })
  1. Ejecuta tu app, que debería funcionar como antes. Sin embargo, ahora la vista de texto de burbuja de texto se usa las expresiones de vinculación para actualizar la IU, no los observadores de LiveData.

Paso 2: Agrega la expresión de vinculación al puntaje y al recuento de palabras

Recursos en expresiones de vinculación de datos

Una expresión de vinculación de datos puede hacer referencia a recursos de la aplicación con la siguiente sintaxis.

Ejemplo:

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

En el ejemplo anterior, al atributo padding se le asigna un valor de largePadding desde el archivo de recursos dimen.xml.

También puedes pasar propiedades de diseño como parámetros de recursos.

Ejemplo:

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

strings.xml

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

En el ejemplo anterior, example_resource es un recurso de strings con el marcador de posición %s. Estás pasando user.lastName como un parámetro de recurso en la expresión de vinculación, donde user es una variable de diseño.

En este paso, agregarás expresiones de vinculación a las vistas de texto y recuento de palabras, y pasarás los parámetros de recursos. Este paso es similar al que usaste anteriormente para textView_unscrambled_word.

  1. En game_fragment.xml, actualiza el atributo text para la vista de texto word_count con la siguiente expresión de vinculación. Usa el recurso de strings word_count y pasa gameViewModel.currentWordCount y maxNoOfWords como parámetros de recursos.
<TextView
   android:id="@+id/word_count"
   ...
   android:text="@{@string/word_count(gameViewModel.currentWordCount, maxNoOfWords)}"
   .../>
  1. Actualiza el atributo text para la vista de texto score con la siguiente expresión de vinculación. Usa el recurso de strings score y pasa gameViewModel.score como un parámetro de recursos.
<TextView
   android:id="@+id/score"
   ...
   android:text="@{@string/score(gameViewModel.score)}"
   ... />
  1. Quita observadores LiveData de GameFragment. Ya no los necesitas; las expresiones de vinculación actualizan la IU cuando cambia el LiveData correspondiente.

Quita:

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. Ejecuta tu app y juega con algunas palabras. Ahora tu código usa LiveData y expresiones de vinculación para actualizar la IU.

7880e60dc0a6f95c.png 9ef2fdf21ffa5c99.png

¡Felicitaciones! Aprendiste a usar LiveData con observadores de LiveData y LiveData con expresiones de vinculación.

Como estuviste aprendiendo a lo largo de este curso, deseas compilar apps que sean accesibles para tantos usuarios como sea posible. Algunos usuarios pueden usar Talkback para acceder a tu app y navegar por ella. TalkBack es el lector de pantalla de Google incluido en los dispositivos Android. Esta función emite comentarios por voz para que puedas usar el dispositivo sin mirar la pantalla.

Cuando TalkBack esté habilitado, asegúrate de que un jugador pueda jugar.

  1. Para habilitar TalkBack en tu dispositivo, sigue estas instrucciones.
  2. Regresa a la app de Unscramble.
  3. Explora tu app con Talkback usando estas instrucciones. Desliza el dedo hacia la derecha para navegar por los elementos de la pantalla en secuencia y desliza el dedo hacia la izquierda para ir en la dirección opuesta. Presiona dos veces en cualquier lugar para realizar una selección. Verifica que puedas llegar a todos los elementos de la app con gestos de deslizamiento.
  4. Asegúrate de que un usuario de TalkBack pueda navegar a cada elemento de la pantalla.
  5. Observa que TalkBack intenta leer la palabra desordenada como una palabra. Esto puede resultar confuso para el jugador, ya que no es una palabra real.
  6. Para obtener una mejor experiencia del usuario, TalkBack podría leer en voz alta los caracteres individuales de la palabra desordenada. Dentro de GameViewModel, convierte la palabra desordenada String en una string Spannable. Una string Spannable es una string con información adicional adjunta. En este caso, queremos asociar la string con un TtsSpan de TYPE_VERBATIM, para que el motor de texto a voz lea en voz alta la palabra desordenada, carácter por carácter.
  7. En GameViewModel,, usa el siguiente código para modificar cómo se declara la variable currentScrambledWord:
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
    }
}

Ahora, esta variable es un LiveData<Spannable> en lugar de LiveData<String>. No tienes que preocuparte por comprender todos los detalles de cómo funciona el proceso, pero la implementación usa una transformación LiveData para convertir la palabra desordenada actual String en una string Spannable que pueda manejar apropiadamente el servicio de accesibilidad. En el siguiente codelab, obtendrás más información sobre las transformaciones LiveData, que te permiten mostrar una instancia LiveData diferente según el valor de LiveData correspondiente.

  1. Ejecuta la app de Unscramble, explora tu app con TalkBack. TalkBack debería leer los caracteres individuales de la palabra desordenada.

Para obtener más información sobre cómo mejorar la accesibilidad de tu app, consulta estos principios.

Se recomienda borrar el código no alcanzado, no utilizado o no deseado para el código de la solución. Esto facilita el mantenimiento del código y ayuda a los nuevos compañeros de equipo a entenderlo mejor.

  1. En GameFragment, borra los métodos getNextScrambledWord() y onDetach().
  2. En el método GameViewModel, borra onCleared().
  3. Borra las importaciones sin usar, en la parte superior de los archivos de origen. Aparecerán inhabilitadas.

Si ya no necesitas las instrucciones de registro, puedes borrarlas del código si lo prefieres.

  1. [Opcional] Borra las declaraciones Log de los archivos de origen (GameFragment.kt y GameViewModel.kt) que agregaste en el codelab anterior para comprender el ciclo de vida de ViewModel.

El código de solución para este codelab se encuentra en el proyecto que se muestra a continuación.

A fin de obtener el código necesario para este codelab y abrirlo en Android Studio, haz lo siguiente:

Obtén el código

  1. Haz clic en la URL proporcionada. Se abrirá la página de GitHub del proyecto en un navegador.
  2. En esa página, haz clic en el botón Code, que abre un cuadro de diálogo.

5b0a76c50478a73f.png

  1. En el cuadro de diálogo, haz clic en el botón Download ZIP para guardar el proyecto en tu computadora. Espera a que se complete la descarga.
  2. Ubica el archivo en tu computadora (probablemente en la carpeta Descargas).
  3. Haz doble clic en el archivo ZIP para descomprimirlo. Se creará una carpeta nueva con los archivos del proyecto.

Abre el proyecto en Android Studio

  1. Inicia Android Studio.
  2. En la ventana Welcome to Android Studio, haz clic en Open an existing Android Studio project.

36cc44fcf0f89a1d.png

Nota: Si Android Studio ya está abierto, selecciona la opción de menú File > New > Import Project.

21f3eec988dcfbe9.png

  1. En el cuadro de diálogo Import Project, navega hasta donde se encuentra la carpeta de proyecto descomprimido (probablemente en Descargas).
  2. Haz doble clic en la carpeta del proyecto.
  3. Espera a que Android Studio abra el proyecto.
  4. Haz clic en el botón Run 11c34fc5e516fb1c.png para compilar y ejecutar la app. Asegúrate de que funcione como se espera.
  5. Explora los archivos del proyecto en la ventana de herramientas Project para ver cómo se configuró la app.
  • LiveData contiene datos; LiveData es un wrapper que se puede usar con cualquier dato.
  • LiveData es observable, lo que significa que se notifica a un observador cuando cambian los datos retenidos por el objeto LiveData.
  • LiveData está optimizado para los ciclos de vida. Cuando adjuntas un observador a LiveData, el observador se asocia con un LifecycleOwner (por lo general, una actividad o un fragmento). LiveData solo actualiza observadores que están en estado de ciclo de vida activo, como STARTED o RESUMED. Puedes obtener más información sobre LiveData y la observación aquí.
  • Las apps pueden detectar los cambios de LiveData desde el diseño mediante la vinculación de datos y las expresiones de vinculación.
  • Las expresiones de vinculación se escriben dentro del diseño en las propiedades del atributo (como android:text) que hacen referencia a las propiedades del diseño.

Entradas de blog