Este codelab es parte del curso Conceptos básicos de Kotlin para Android. Aprovecharás al máximo este curso si trabajas con los codelabs de forma secuencial. Todos los codelabs del curso se enumeran en la página de destino de los codelabs de Android Kotlin Fundamentals.
Introducción
En los codelabs anteriores de esta lección, mejoraste el código de la app de GuessTheWord. Ahora, la app usa objetos ViewModel
, por lo que los datos de la app sobreviven a los cambios de configuración del dispositivo, como las rotaciones de pantalla y los cambios en la disponibilidad del teclado. También agregaste LiveData
observables, por lo que las vistas reciben notificaciones automáticamente cuando cambian los datos observados.
En este codelab, seguirás trabajando con la app de GuessTheWord. Vincularás vistas a las clases ViewModel
de la app para que las vistas de tu diseño se comuniquen directamente con los objetos ViewModel
. (Hasta ahora, en tu app, las vistas se comunicaron indirectamente con el ViewModel
, a través de los fragmentos de la app). Después de integrar la vinculación de datos con los objetos ViewModel
, ya no necesitas controladores de clics en los fragmentos de la app, por lo que los quitas.
También cambiarás la app de GuessTheWord para que use LiveData
como fuente de vinculación de datos para notificar a la IU sobre los cambios en los datos, sin usar métodos de observador de LiveData
.
Conocimientos que ya deberías tener
- Cómo crear apps básicas para Android en Kotlin
- Cómo funcionan los ciclos de vida de actividades y fragmentos
- Cómo usar objetos
ViewModel
en tu app - Cómo almacenar datos con
LiveData
en unViewModel
- Cómo agregar métodos de observador para observar los cambios en los datos de
LiveData
Qué aprenderás
- Cómo usar elementos de la biblioteca de vinculación de datos
- Cómo integrar
ViewModel
con la vinculación de datos - Cómo integrar
LiveData
con la vinculación de datos - Cómo usar las vinculaciones de objetos de escucha para reemplazar los objetos de escucha de clics en un fragmento
- Cómo agregar formato de cadenas a expresiones de vinculación de datos
Actividades
- Las vistas de los diseños de GuessTheWord se comunican de forma indirecta con los objetos
ViewModel
, y usan controladores de IU (fragmentos) para transmitir información. En este codelab, vincularás las vistas de la app a objetosViewModel
para que las vistas se comuniquen directamente con los objetosViewModel
. - Cambias la app para que use
LiveData
como fuente de vinculación de datos. Después de este cambio, los objetosLiveData
notifican a la IU los cambios en los datos, y los métodos de observadorLiveData
ya no son necesarios.
En los codelabs de la lección 5, desarrollarás la app de GuessTheWord, comenzando con el código de inicio. GuessTheWord es un juego de adivinanzas para dos jugadores en el que ambos colaboran para obtener la puntuación más alta posible.
El primer jugador mira las palabras en la app y representa cada una por turnos, asegurándose de no mostrarle la palabra al segundo jugador. El segundo jugador intenta adivinar la palabra.
Para jugar, el primer jugador abre la app en el dispositivo y ve una palabra, por ejemplo, "guitarra", como se muestra en la siguiente captura de pantalla.
El primer jugador representa la palabra, teniendo cuidado de no decirla.
- Cuando el segundo jugador adivina la palabra correctamente, el primer jugador presiona el botón Entendido, lo que aumenta el recuento en uno y muestra la siguiente palabra.
- Si el segundo jugador no puede adivinar la palabra, el primer jugador presiona el botón Omitir, lo que disminuye el recuento en uno y pasa a la siguiente palabra.
- Para finalizar el juego, presiona el botón Finalizar juego. (Esta funcionalidad no se encuentra en el código de partida del primer codelab de la serie).
En este codelab, mejorarás la app de GuessTheWord integrando la vinculación de datos con LiveData
en objetos ViewModel
. Esto automatiza la comunicación entre las vistas del diseño y los objetos ViewModel
, y te permite simplificar tu código con LiveData
.
Pantalla de título | Pantalla del juego | Pantalla de puntuación |
En esta tarea, ubicarás y ejecutarás el código de partida para este codelab. Puedes usar la app de GuessTheWord que compilaste en el codelab anterior como código de partida o descargar una app de partida.
- (Opcional) Si no usas el código del codelab anterior, descarga el código de partida para este codelab. Descomprime el código y abre el proyecto en Android Studio.
- Ejecuta la app y juega.
- Observa que el botón Entendido muestra la siguiente palabra y aumenta la puntuación en uno, mientras que el botón Omitir muestra la siguiente palabra y disminuye la puntuación en uno. El botón End Game finaliza el juego.
- Recorre todas las palabras y observa que la app navega automáticamente a la pantalla de puntuación.
En un codelab anterior, usaste la vinculación de datos como una forma de acceder a las vistas en la app de GuessTheWord con seguridad de tipos. Sin embargo, el verdadero poder de la vinculación de datos radica en hacer lo que sugiere su nombre: vincular datos directamente a los objetos de vista en tu app.
Arquitectura actual de la app
En tu app, las vistas se definen en el diseño XML, y los datos de esas vistas se almacenan en objetos ViewModel
. Entre cada vista y su ViewModel
correspondiente, hay un controlador de IU que actúa como un relé entre ellos.
Por ejemplo:
- El botón Entendido se define como una vista
Button
en el archivo de diseñogame_fragment.xml
. - Cuando el usuario presiona el botón Entendido, un objeto de escucha de clics en el fragmento
GameFragment
llama al objeto de escucha de clics correspondiente enGameViewModel
. - La puntuación se actualiza en
GameViewModel
.
La vista Button
y el GameViewModel
no se comunican directamente, sino que necesitan el objeto de escucha de clics que se encuentra en el GameFragment
.
ViewModel que se pasa a la vinculación de datos
Sería más sencillo si las vistas del diseño se comunicaran directamente con los datos de los objetos ViewModel
, sin depender de los controladores de IU como intermediarios.
Los objetos ViewModel
contienen todos los datos de la IU en la app de GuessTheWord. Si pasas objetos ViewModel
a la vinculación de datos, puedes automatizar parte de la comunicación entre las vistas y los objetos ViewModel
.
En esta tarea, asociarás las clases GameViewModel
y ScoreViewModel
con sus diseños XML correspondientes. También configuraste vinculaciones de objetos de escucha para controlar los eventos de clic.
Paso 1: Agrega la vinculación de datos para GameViewModel
En este paso, asociarás GameViewModel
con el archivo de diseño correspondiente, game_fragment.xml
.
- En el archivo
game_fragment.xml
, agrega una variable de vinculación de datos del tipoGameViewModel
. Si tienes errores en Android Studio, limpia y vuelve a compilar el proyecto.
<layout ...>
<data>
<variable
name="gameViewModel"
type="com.example.android.guesstheword.screens.game.GameViewModel" />
</data>
<androidx.constraintlayout...
- En el archivo
GameFragment
, pasaGameViewModel
a la vinculación de datos.
Para ello, asignaviewModel
a la variablebinding.gameViewModel
, que declaraste en el paso anterior. Coloca este código dentro deonCreateView()
, después de que se inicialiceviewModel
. Si tienes errores en Android Studio, limpia y vuelve a compilar el proyecto.
// Set the viewmodel for databinding - this allows the bound layout access
// to all the data in the ViewModel
binding.gameViewModel = viewModel
Paso 2: Usa vinculaciones de objetos de escucha para el control de eventos
Las vinculaciones de objetos de escucha son expresiones de vinculación que se ejecutan cuando se activan eventos como onClick()
, onZoomIn()
o onZoomOut()
. Las vinculaciones de los objetos de escucha se escriben como expresiones lambda.
La vinculación de datos crea un objeto de escucha y lo establece en la vista. Cuando se produce el evento que se escucha, el objeto de escucha evalúa la expresión lambda. Las vinculaciones de los objetos de escucha funcionan con la versión 2.0 o posterior del complemento de Android para Gradle. Para obtener más información, consulta Diseños y expresiones de vinculación.
En este paso, reemplazarás los objetos de escucha de clics en GameFragment
por vinculaciones de objetos de escucha en el archivo game_fragment.xml
.
- En
game_fragment.xml
, agrega el atributoonClick
askip_button
. Define una expresión de vinculación y llama al métodoonSkip()
enGameViewModel
. Esta expresión de vinculación se denomina vinculación de escucha.
<Button
android:id="@+id/skip_button"
...
android:onClick="@{() -> gameViewModel.onSkip()}"
... />
- De manera similar, vincula el evento de clic de
correct_button
al métodoonCorrect
()
enGameViewModel
.
<Button
android:id="@+id/correct_button"
...
android:onClick="@{() -> gameViewModel.onCorrect()}"
... />
- Vincula el evento de clic de
end_game_button
al métodoonGameFinish
()
enGameViewModel
.
<Button
android:id="@+id/end_game_button"
...
android:onClick="@{() -> gameViewModel.onGameFinish()}"
... />
- En
GameFragment
, quita las instrucciones que establecen los objetos de escucha de clics y las funciones que llaman los objetos de escucha de clics. Ya no los necesitas.
Código que debes quitar:
binding.correctButton.setOnClickListener { onCorrect() }
binding.skipButton.setOnClickListener { onSkip() }
binding.endGameButton.setOnClickListener { onEndGame() }
/** Methods for buttons presses **/
private fun onSkip() {
viewModel.onSkip()
}
private fun onCorrect() {
viewModel.onCorrect()
}
private fun onEndGame() {
gameFinished()
}
Paso 3: Agrega la vinculación de datos para ScoreViewModel
En este paso, asociarás ScoreViewModel
con el archivo de diseño correspondiente, score_fragment.xml
.
- En el archivo
score_fragment.xml
, agrega una variable de vinculación del tipoScoreViewModel
. Este paso es similar al que usaste anteriormente paraGameViewModel
.
<layout ...>
<data>
<variable
name="scoreViewModel"
type="com.example.android.guesstheword.screens.score.ScoreViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
- En
score_fragment.xml
, agrega el atributoonClick
aplay_again_button
. Define una vinculación de objeto de escucha y llama al métodoonPlayAgain()
enScoreViewModel
.
<Button
android:id="@+id/play_again_button"
...
android:onClick="@{() -> scoreViewModel.onPlayAgain()}"
... />
- En
ScoreFragment
, dentro deonCreateView()
, inicializaviewModel
. Luego, inicializa la variable de vinculaciónbinding.scoreViewModel
.
viewModel = ...
binding.scoreViewModel = viewModel
- En
ScoreFragment
, quita el código que configura el objeto de escucha de clics para elplayAgainButton
. Si Android Studio muestra un error, limpia y vuelve a compilar el proyecto.
Código que debes quitar:
binding.playAgainButton.setOnClickListener { viewModel.onPlayAgain() }
- Ejecuta la app. La app debería funcionar como antes, pero ahora las vistas de botones se comunican directamente con los objetos
ViewModel
. Las vistas ya no se comunican a través de los controladores de clics de botones enScoreFragment
.
Soluciona problemas de mensajes de error de vinculación de datos
Cuando una app usa la vinculación de datos, el proceso de compilación genera clases intermedias que se usan para la vinculación de datos. Una app puede tener errores que Android Studio no detecta hasta que intentas compilarla, por lo que no ves advertencias ni código rojo mientras escribes el código. Sin embargo, en tiempo de compilación, obtienes errores crípticos que provienen de las clases intermedias generadas.
Si recibes un mensaje de error críptico, haz lo siguiente:
- Observa con atención el mensaje en el panel Build de Android Studio. Si ves una ubicación que termina en
databinding
, hay un error con la vinculación de datos. - En el archivo XML de diseño, busca errores en los atributos
onClick
que usan vinculación de datos. Busca la función a la que llama la expresión lambda y asegúrate de que exista. - En la sección
<data>
del XML, verifica la ortografía de la variable de vinculación de datos.
Por ejemplo, observa el error ortográfico en el nombre de la función onCorrect()
en el siguiente valor del atributo:
android:onClick="@{() -> gameViewModel.onCorrectx()}"
También ten en cuenta la falta de ortografía de gameViewModel
en la sección <data>
del archivo en formato XML:
<data>
<variable
name="gameViewModelx"
type="com.example.android.guesstheword.screens.game.GameViewModel" />
</data>
Android Studio no detecta errores como estos hasta que compilas la app. Luego, el compilador muestra un mensaje de error como el siguiente:
error: cannot find symbol import com.example.android.guesstheword.databinding.GameFragmentBindingImpl" symbol: class GameFragmentBindingImpl location: package com.example.android.guesstheword.databinding
La vinculación de datos funciona bien con LiveData
que se usa con objetos ViewModel
. Ahora que agregaste la vinculación de datos a los objetos ViewModel
, puedes incorporar LiveData
.
En esta tarea, cambiarás la app de GuessTheWord para que use LiveData
como fuente de vinculación de datos para notificar a la IU sobre los cambios en los datos, sin usar los métodos de observador de LiveData
.
Paso 1: Agrega LiveData de palabras al archivo game_fragment.xml
En este paso, vinculas la vista de texto de la palabra actual directamente al objeto LiveData
en ViewModel
.
- En
game_fragment.xml
, agrega el atributoandroid:text
a la vista de textoword_text
.
Establécelo en el objeto LiveData
, word
del GameViewModel
, con la variable de vinculación, gameViewModel
.
<TextView
android:id="@+id/word_text"
...
android:text="@{gameViewModel.word}"
... />
Ten en cuenta que no es necesario que uses word.value
. En su lugar, puedes usar el objeto LiveData
real. El objeto LiveData
muestra el valor actual de word
. Si el valor de word
es nulo, el objeto LiveData
muestra una cadena vacía.
- En
GameFragment
, enonCreateView()
, después de inicializargameViewModel
, establece la actividad actual como el propietario del ciclo de vida de la variablebinding
. Esto define el alcance del objetoLiveData
anterior, lo que permite que el objeto actualice automáticamente las vistas en el diseño,game_fragment.xml
.
binding.gameViewModel = ...
// Specify the current activity as the lifecycle owner of the binding.
// This is used so that the binding can observe LiveData updates
binding.lifecycleOwner = this
- En
GameFragment
, quita el observador para elLiveData
word
.
Código que debes quitar:
/** Setting up LiveData observation relationship **/
viewModel.word.observe(this, Observer { newWord ->
binding.wordText.text = newWord
})
- Ejecuta la app y juega. Ahora, la palabra actual se actualiza sin un método de observador en el controlador de IU.
Paso 2: Agrega LiveData de la puntuación al archivo score_fragment.xml
En este paso, vincularás el LiveData
score
a la vista de texto de la puntuación en el fragmento de puntuación.
- En
score_fragment.xml
, agrega el atributoandroid:text
a la vista de texto de la puntuación. AsignascoreViewModel.score
al atributotext
. Comoscore
es un número entero, conviértelo en una cadena conString.valueOf()
.
<TextView
android:id="@+id/score_text"
...
android:text="@{String.valueOf(scoreViewModel.score)}"
... />
- En
ScoreFragment
, después de inicializarscoreViewModel
, establece la actividad actual como el propietario del ciclo de vida de la variablebinding
.
binding.scoreViewModel = ...
// Specify the current activity as the lifecycle owner of the binding.
// This is used so that the binding can observe LiveData updates
binding.lifecycleOwner = this
- En
ScoreFragment
, quita el observador del objetoscore
.
Código que debes quitar:
// Add observer for score
viewModel.score.observe(this, Observer { newScore ->
binding.scoreText.text = newScore.toString()
})
- Ejecuta la app y juega. Observa que la puntuación en el fragmento de puntuación se muestra correctamente, sin un observador en el fragmento de puntuación.
Paso 3: Agrega formato de cadenas con vinculación de datos
En el diseño, puedes agregar formato de cadenas junto con la vinculación de datos. En esta tarea, formatearás la palabra actual para agregarle comillas. También le das formato a la cadena de puntuación para agregarle el prefijo Puntuación actual, como se muestra en la siguiente imagen.
- En
string.xml
, agrega las siguientes cadenas, que usarás para dar formato a las vistas de textoword
yscore
.%s
y%d
son los marcadores de posición para la palabra actual y la puntuación actual.
<string name="quote_format">\"%s\"</string>
<string name="score_format">Current Score: %d</string>
- En
game_fragment.xml
, actualiza el atributotext
de la vista de textoword_text
para usar el recurso de cadenaquote_format
. PasagameViewModel.word
. Esto pasa la palabra actual como un argumento a la cadena de formato.
<TextView
android:id="@+id/word_text"
...
android:text="@{@string/quote_format(gameViewModel.word)}"
... />
- Aplica el formato a la vista de texto
score
de forma similar aword_text
. Engame_fragment.xml
, agrega el atributotext
a la vista de textoscore_text
. Usa el recurso de cadenascore_format
, que toma un argumento numérico, representado por el marcador de posición%d
. Pasa el objetoLiveData
,score
, como argumento a esta cadena de formato.
<TextView
android:id="@+id/score_text"
...
android:text="@{@string/score_format(gameViewModel.score)}"
... />
- En la clase
GameFragment
, dentro del métodoonCreateView()
, quita el código del observadorscore
.
Código que debes quitar:
viewModel.score.observe(this, Observer { newScore ->
binding.scoreText.text = newScore.toString()
})
- Limpia, vuelve a compilar y ejecuta tu app, y luego juega. Observa que la palabra actual y la puntuación tienen el formato adecuado en la pantalla del juego.
¡Felicitaciones! Integraste LiveData
y ViewModel
con la vinculación de datos en tu app. Esto permite que las vistas de tu diseño se comuniquen directamente con el ViewModel
, sin usar controladores de clics en el fragmento. También usaste objetos LiveData
como fuente de vinculación de datos para notificar automáticamente a la IU sobre cambios en los datos, sin los métodos de observador de LiveData
.
Proyecto de Android Studio: GuessTheWord
- La biblioteca de vinculación de datos funciona a la perfección con los componentes de arquitectura de Android, como
ViewModel
yLiveData
. - Los diseños de tu app se pueden vincular a los datos de los componentes de arquitectura, que ya te ayudan a administrar el ciclo de vida del controlador de la IU y a notificar cambios en los datos.
Vinculación de datos de ViewModel
- Puedes asociar un
ViewModel
con un diseño usando la vinculación de datos. - Los objetos
ViewModel
contienen los datos de la IU. Si pasas objetosViewModel
a la vinculación de datos, puedes automatizar parte de la comunicación entre las vistas y los objetosViewModel
.
Sigue estos pasos para asociar un ViewModel
con un diseño:
- En el archivo de diseño, agrega una variable de vinculación de datos del tipo
ViewModel
.
<data>
<variable
name="gameViewModel"
type="com.example.android.guesstheword.screens.game.GameViewModel" />
</data>
- En el archivo
GameFragment
, pasaGameViewModel
a la vinculación de datos.
binding.gameViewModel = viewModel
Vinculaciones de objetos de escucha
- Las vinculaciones de objetos de escucha son expresiones de vinculación en el diseño que se ejecutan cuando se activan eventos de clic, como
onClick()
. - Las vinculaciones de los objetos de escucha se escriben como expresiones lambda.
- Con las vinculaciones de objetos de escucha, reemplazas los objetos de escucha de clics en los controladores de la IU por vinculaciones de objetos de escucha en el archivo de diseño.
- La vinculación de datos crea un objeto de escucha y lo establece en la vista.
android:onClick="@{() -> gameViewModel.onSkip()}"
Cómo agregar LiveData a la vinculación de datos
- Los objetos
LiveData
se pueden usar como fuente de vinculación de datos para notificar automáticamente a la IU sobre cambios en los datos. - Puedes vincular la vista directamente al objeto
LiveData
enViewModel
. Cuando cambia elLiveData
en elViewModel
, las vistas del diseño se pueden actualizar automáticamente, sin los métodos de observador en los controladores de la IU.
android:text="@{gameViewModel.word}"
- Para que funcione la vinculación de datos de
LiveData
, establece la actividad actual (el controlador de IU) como el propietario del ciclo de vida de la variablebinding
en el controlador de IU.
binding.lifecycleOwner = this
Formateo de cadenas con vinculación de datos
- Con la vinculación de datos, puedes dar formato a un recurso de cadena con marcadores de posición como
%s
para cadenas y%d
para números enteros. - Para actualizar el atributo
text
de la vista, pasa el objetoLiveData
como argumento a la cadena de formato.
android:text="@{@string/quote_format(gameViewModel.word)}"
Curso de Udacity:
Documentación para desarrolladores de Android:
En esta sección, se enumeran las posibles actividades para el hogar para los alumnos que trabajan en este codelab como parte de un curso dirigido por un instructor. Depende del instructor hacer lo siguiente:
- Si es necesario, asigna una tarea.
- Comunicarles a los alumnos cómo enviar las actividades para el hogar.
- Califica las actividades para el hogar.
Los instructores pueden usar estas sugerencias en la medida que quieran y deben asignar cualquier otra actividad para el hogar que consideren apropiada.
Si estás trabajando en este codelab por tu cuenta, usa estas actividades para el hogar para probar tus conocimientos.
Responde estas preguntas:
Pregunta 1
¿Cuál de las siguientes afirmaciones no es verdadera sobre las vinculaciones de objetos de escucha?
- Las vinculaciones de los objetos de escucha son expresiones de vinculación que se ejecutan cuando ocurre un evento.
- Las vinculaciones de los objetos de escucha funcionan con todas las versiones del complemento de Android para Gradle.
- Las vinculaciones de los objetos de escucha se escriben como expresiones lambda.
- Las vinculaciones de objetos de escucha son similares a las referencias de métodos, pero te permiten ejecutar expresiones de vinculación de datos arbitrarias.
Pregunta 2
Supón que tu app incluye este recurso de cadena:<string name="generic_name">Hello %s</string>
¿Cuál de las siguientes opciones es la sintaxis correcta para aplicar formato a la cadena con la expresión de vinculación de datos?
android:text= "@{@string/generic_name(user.name)}"
android:text= "@{string/generic_name(user.name)}"
android:text= "@{@generic_name(user.name)}"
android:text= "@{@string/generic_name,user.name}"
Pregunta 3
¿Cuándo se evalúa y ejecuta una expresión que vincula un objeto de escucha?
- Cuando se modifican los datos que retiene el
LiveData
- Cuando se vuelve a crear una actividad con un cambio de configuración
- Cuando se produce un evento como
onClick()
- Cuando la actividad pasa a segundo plano
Comienza la próxima lección:
Para obtener vínculos a otros codelabs de este curso, consulta la página de destino de los codelabs de Conceptos básicos de Kotlin para Android.