1. Antes de comenzar
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 para 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
yMutableLiveData
en tu app - Cómo encapsular los datos almacenados en un
ViewModel
conLiveData
- 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 desordenadas se actualizan automáticamente.
Qué necesitarás
- 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 partida para este codelab
En este codelab, se usa la app de Unscramble que creaste en el codelab anterior (Cómo almacenar datos en ViewModel) como código de partida.
2. Descripción general de la app de inicio
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
.
3. Qué es LiveData
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 objetoLiveData
.LiveData
está optimizado para los ciclos de vida. Cuando adjuntas un observador aLiveData
, el observador se asocia con unLifecycleOwner
(por lo general, una actividad o un fragmento). ElLiveData
solo actualiza los observadores que tienen un estado de ciclo de vida activo, comoSTARTED
oRESUMED
. Puedes obtener más información sobreLiveData
y la observación aquí.
Actualización de la IU en el código de partida
En el código de partida, 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.
4. Cómo agregar LiveData a la palabra desordenada actual
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.
- En
GameViewModel
, cambia el tipo de la variable_currentScrambledWord
aMutableLiveData
<String>
.LiveData
yMutableLiveData
son clases genéricas, por lo que debes especificar el tipo de datos que retienen. - Cambia el tipo de variable de
_currentScrambledWord
aval
porque el valor del objetoLiveData
/MutableLiveData
se mantendrá igual, y solo cambiarán los datos almacenados en el objeto.
private val _currentScrambledWord = MutableLiveData<String>()
- Cambia el tipo del campo de copia de seguridad
currentScrambledWord
aLiveData<String>
, ya que es inmutable. Android Studio mostrará algunos errores que corregirás en los próximos pasos.
val currentScrambledWord: LiveData<String>
get() = _currentScrambledWord
- Para acceder a los datos de un objeto
LiveData
, usa la propiedadvalue
. EnGameViewModel
del métodogetNextWord()
, dentro del bloqueelse
, cambia la referencia de_currentScrambledWord
a_currentScrambledWord.value
.
private fun getNextWord() {
...
} else {
_currentScrambledWord.value = String(tempWord)
...
}
}
5. Cómo adjuntar un observador al objeto LiveData
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
solo recibirá una notificación cuando GameFragment
tenga el estado STARTED
o RESUMED
.
- En
GameFragment
, borra el métodoupdateNextWordOnScreen()
y todas las llamadas a él. No necesitas este método, ya que adjuntarás un observador aLiveData
. - En
onSubmitWord()
, modifica el bloqueif-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)
}
}
- Adjunta un observador para
currentScrambledWord
LiveData
. EnGameFragment
al final de la devolución de llamadaonViewCreated()
, llama al métodoobserve()
encurrentScrambledWord
.
// 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.
- Pasa
viewLifecycleOwner
como el primer parámetro al métodoobserve()
. El objetoviewLifecycleOwner
representa el ciclo de vida de la vista del fragmento. Este parámetro ayuda a queLiveData
conozca el ciclo de vida deGameFragment
y notifique al observador solo cuandoGameFragment
tenga estados activos (STARTED
oRESUMED
). - 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 { }.
- 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
})
- 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étodoupdateNextWordOnScreen()
.
6. Cómo adjuntar un observador a la puntuación y al recuento de palabras
Al igual que en la tarea anterior, en esta 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
- En
GameViewModel
, cambia el tipo de las variables de clase_score
y_currentWordCount
aval
. - Cambia el tipo de datos de las variables
_score
y_currentWordCount
aMutableLiveData
e inicialízalos a0
. - 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
- En
GameViewModel
, al principio del métodoreinitializeData()
, 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()
}
- En
GameViewModel
, dentro del métodonextWord()
, cambia la referencia de_currentWordCount
a_currentWordCount.
value!!
.
fun nextWord(): Boolean {
return if (_currentWordCount.value!! < MAX_NO_OF_WORDS) {
getNextWord()
true
} else false
}
- En
GameViewModel
, dentro de los métodosincreaseScore()
ygetNextWord()
, 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, esLiveData
. Lo corregirás en los pasos siguientes. - 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)
}
- 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)
}
}
- En
GameFragment
, accede al valor descore
con la propiedadvalue
. Dentro del métodoshowFinalScoreDialog()
, cambiaviewModel.score
aviewModel.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
.
- En
GameFragment
dentro del métodoonViewCreated()
, 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)
- En
GameFragment
, al final del métodoonViewCreated()
, adjunta un observador descore
. PasaviewLifecycleOwner
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)
})
- Al final del método
onViewCreated()
, adjunta un observador paracurrentWordCount
LiveData
. PasaviewLifecycleOwner
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 elMAX_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
.
- 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
ycurrentWordCount
sonLiveData
, y se llama automáticamente a los observadores correspondientes cuando cambia el valor subyacente.
7. Cómo usar LiveData con la vinculación de datos
En las tareas anteriores, tu app escuchó 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 app con 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 vista (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
- En el archivo
build.gradle(Module)
, habilita la propiedaddataBinding
en la secciónbuildFeatures
.
Reemplaza
buildFeatures {
viewBinding = true
}
con
buildFeatures {
dataBinding = true
}
Realiza una sincronización de Gradle cuando Android Studio lo solicite
- 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 archivobuild.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.
- Abre
game_fragment.xml
y selecciona la pestaña code. - 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 conxmlns:
) 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
), selecciona Show Context Actions > Convert to data binding layout.
- 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>
- En
GameFragment
, al principio del métodoonCreateView()
, cambia la creación de instancias de la variablebinding
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)
- Compila el código. Deberías poder hacerlo sin problemas. Ahora tu app usa la vinculación de datos, y las vistas de diseño pueden acceder a los datos de esta.
8. Cómo agregar variables de vinculación de datos
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.
- En
game_fragment.xml
, dentro de la etiqueta<data>
y una etiqueta secundaria llamada<variable>
, declara una propiedad con el nombregameViewModel
y del tipoGameViewModel
. Usarás esto para vincular los datos enViewModel
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.
- Debajo de la declaración
gameViewModel
, agrega otra variable dentro de la etiqueta<data>
del tipoInteger
y asígnale el nombremaxNoOfWords
. 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>
- En
GameFragment
al comienzo del métodoonViewCreated()
, inicializa las variables de diseñogameViewModel
ymaxNoOfWords
.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.gameViewModel = viewModel
binding.maxNoOfWords = MAX_NO_OF_WORDS
...
}
LiveData
es observable y está optimizada para ciclos de vida, por lo que debes pasar el propietario del ciclo de vida al diseño. EnGameFragment
, dentro del métodoonViewCreated()
, 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
.
9. Cómo usar 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. 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
.
- En
game_fragment.xml
, agrega un atributotext
a la vista de textotextView_unscrambled_word
. Usa la nueva variable de diseño,gameViewModel
, y asigna@{gameViewModel.currentScrambledWord}
al atributotext
.
<TextView
android:id="@+id/textView_unscrambled_word"
...
android:text="@{gameViewModel.currentScrambledWord}"
.../>
- En
GameFragment
, quita el código del observadorLiveData
paracurrentScrambledWord
: ya no necesitas el código del observador en el fragmento. El diseño recibe las actualizaciones de los cambios enLiveData
directamente.
Quita:
viewModel.currentScrambledWord.observe(viewLifecycleOwner,
{ newWord ->
binding.textViewUnscrambledWord.text = newWord
})
- Ejecuta tu app, que debería funcionar como antes. Sin embargo, ahora la vista de texto de la palabra desordenada 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
.
- En
game_fragment.xml
, actualiza el atributotext
para la vista de textoword_count
con la siguiente expresión de vinculación. Usa el recurso de stringsword_count
y pasagameViewModel.currentWordCount
ymaxNoOfWords
como parámetros de recursos.
<TextView
android:id="@+id/word_count"
...
android:text="@{@string/word_count(gameViewModel.currentWordCount, maxNoOfWords)}"
.../>
- Actualiza el atributo
text
para la vista de textoscore
con la siguiente expresión de vinculación. Usa el recurso de stringsscore
y pasagameViewModel.score
como un parámetro de recursos.
<TextView
android:id="@+id/score"
...
android:text="@{@string/score(gameViewModel.score)}"
... />
- Quita observadores
LiveData
deGameFragment
. Ya no los necesitas, las expresiones de vinculación actualizan la IU cuando cambia elLiveData
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)
})
- Ejecuta tu app y juega con algunas palabras. Ahora tu código usa
LiveData
y expresiones de vinculación para actualizar la IU.
¡Felicitaciones! Aprendiste a usar LiveData
con observadores de LiveData
y LiveData
con expresiones de vinculación.
10. Cómo probar la app de Unscramble con TalkBack habilitado
Como estuviste aprendiendo a lo largo de este curso, debes 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.
- Para habilitar TalkBack en tu dispositivo, sigue estas instrucciones.
- Regresa a la app de Unscramble.
- 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.
- Asegúrate de que un usuario de TalkBack pueda navegar a cada elemento de la pantalla.
- 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.
- 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 desordenadaString
en una stringSpannable
. Una string Spannable es una string con información adicional adjunta. En este caso, queremos asociar la string con unTtsSpan
deTYPE_VERBATIM
para que el motor de texto a voz lea en voz alta la palabra desordenada, carácter por carácter. - En
GameViewModel
, usa el siguiente código para modificar cómo se declara la variablecurrentScrambledWord
:
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.
- Ejecuta la app de Unscramble, explora tu app con TalkBack. TalkBack debería leer los caracteres individuales de la palabra desordenada.
Si quieres obtener más información para mejorar la accesibilidad de tu app, consulta estos principios.
11. Cómo borrar el código sin usar
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.
- En
GameFragment
, borra los métodosgetNextScrambledWord()
yonDetach()
. - En el método
GameViewModel
, borraonCleared()
. - 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.
- [Opcional] Borra las declaraciones
Log
de los archivos de origen (GameFragment.kt
yGameViewModel.kt
) que agregaste en el codelab anterior para comprender el ciclo de vida deViewModel
.
12. Código de solución
El código de solución para este codelab se encuentra en el proyecto que se muestra a continuación.
- Navega a la página provista del repositorio de GitHub del proyecto.
- Verifica que el nombre de la rama coincida con el especificado en el codelab. Por ejemplo, en la siguiente captura de pantalla, el nombre de la rama es main.
- En la página de GitHub de este proyecto, haz clic en el botón Code, el cual abre una ventana emergente.
- En la ventana emergente, haz clic en el botón Download ZIP para guardar el proyecto en tu computadora. Espera a que se complete la descarga.
- Ubica el archivo en tu computadora (probablemente en la carpeta Descargas).
- 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
- Inicia Android Studio.
- En la ventana Welcome to Android Studio, haz clic en Open.
Nota: Si Android Studio ya está abierto, selecciona la opción de menú File > Open.
- En el navegador de archivos, ve hasta donde se encuentra la carpeta del proyecto descomprimida (probablemente en Descargas).
- Haz doble clic en la carpeta del proyecto.
- Espera a que Android Studio abra el proyecto.
- Haz clic en el botón Run para compilar y ejecutar la app. Asegúrate de que funcione como se espera.
13. Resumen
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 objetoLiveData
.LiveData
está optimizado para los ciclos de vida. Cuando adjuntas un observador aLiveData
, el observador se asocia con unLifecycleOwner
(por lo general, una actividad o un fragmento). LiveData solo actualiza observadores que están en estado de ciclo de vida activo, comoSTARTED
oRESUMED
. Puedes obtener más información sobreLiveData
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.
14. Más información
- Descripción general de LiveData
- Referencia de la API de LiveDataObserver
- Vinculación de datos
- Vinculación de datos en dos direcciones
Entradas de blog