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 en secuencia. Todos los codelabs del curso se detallan en la página de destino de codelabs sobre los aspectos básicos de Kotlin para Android.
Introducción
En los codelabs anteriores de esta lección, se mejoró el código para la app de GuessTheWord. La app ahora usa objetos ViewModel
, de modo 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 se notifican automáticamente cuando cambian los datos observados.
En este codelab, seguirás trabajando con la app de GuessTheWord. Vincularás las vistas con 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 han comunicado indirectamente con la ViewModel
, a través de los fragmentos de la app). Una vez que integras 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 usar LiveData
como la fuente de vinculación de datos a fin de notificar a la IU sobre cambios en los datos, sin usar los 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 las actividades y los fragmentos
- Cómo usar objetos
ViewModel
en tu app - Cómo almacenar datos mediante
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 vinculaciones de objetos de escucha para reemplazar los objetos de escucha de clics en un fragmento
- Cómo agregar formato de strings a expresiones de vinculación de datos
Actividades
- Las vistas de los diseños de GuessTheWord se comunican indirectamente con los objetos
ViewModel
, mediante controladores de IU (fragmentos) para retransmitir información. En este codelab, vinculas las vistas de la app con los objetosViewModel
para que estas se comuniquen directamente con los objetosViewModel
. - Cambia la app para usar
LiveData
como la fuente de vinculación de datos. Después de este cambio, los objetosLiveData
notifican a la IU sobre los cambios en los datos y ya no se necesitan los métodos del observador deLiveData
.
En los codelabs de la clase 5, desarrollarás la app de GuessTheWord, que comienza con un código de inicio. GuessTheWord es un juego de dos carpas con dos jugadores en el que los jugadores colaboran para obtener la puntuación más alta posible.
El primer jugador mira las palabras en la app y actúa a su vez de manera individual, 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, como "guitarra", como se muestra en la siguiente captura de pantalla.
El primer jugador representa la palabra y tiene cuidado de no decirla realmente.
- Cuando el segundo jugador adivina la palabra correctamente, el primero presiona el botón Entendido, lo que aumenta el recuento en uno y muestra la palabra siguiente.
- Si el segundo jugador no puede adivinar la palabra, el primer jugador presiona el botón Omitir, lo que disminuye la cantidad de palabras y pasa a la siguiente palabra.
- Para finalizar el juego, presiona el botón Finalizar juego. (Esta función no se encuentra en el código de inicio para el 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 de ViewModel
, y te permite simplificar tu código mediante LiveData
.
Pantalla de título | Pantalla del juego | Pantalla de puntuación |
En esta tarea, ubicarás y ejecutarás tu código de inicio para este codelab. Puedes usar la app de GuessTheWord que creaste en el codelab anterior como código de inicio, o bien descargar una app de inicio.
- Si no usas el código del codelab anterior, descarga el código de inicio para este codelab (opcional). Descomprime el código y abre el proyecto en Android Studio.
- Ejecuta la app y comienza a jugar.
- Ten en cuenta que el botón Entendido muestra la palabra siguiente y aumenta la puntuación en uno, mientras que el botón Omitir muestra la palabra siguiente y disminuye la puntuación en uno. El botón End Game finaliza el juego.
- Desplázate por 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 manera de acceder a las vistas de manera segura en función de la app de GuessTheWord. Sin embargo, el verdadero poder de la vinculación de datos recae en lo que el nombre sugiere: la vinculación de datos directamente con 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 estas se guardan en objetos ViewModel
. Entre cada vista y su ViewModel
correspondiente, hay un controlador de IU que actúa como una retransmisión entre ellas.
Por ejemplo:
- El botón Got It (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 el
GameViewModel
.
La vista Button
y GameViewModel
no se comunican directamente; necesitan el objeto de escucha de clics en GameFragment
.
ViewModel pasado 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. Al pasar 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 configurarás vinculaciones de objetos de escucha para controlar eventos de clic.
Paso 1: Agrega la vinculación de datos para GameViewModel
En este paso, asocias 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 controlar 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 configura en la vista. Cuando sucede el evento escuchado, el objeto de escucha evalúa la expresión lambda. Las vinculaciones de 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, reemplazas 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 del objeto 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
con el métodoonGameFinish
()
enGameViewModel
.
<Button
android:id="@+id/end_game_button"
...
android:onClick="@{() -> gameViewModel.onGameFinish()}"
... />
- En
GameFragment
, quita las sentencias que establecen los objetos de escucha de clics y las funciones a las que llaman los objetos de escucha de clics. Ya no los necesitas.
Código para 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, asocias 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 objetos de escucha y llama al métodoonPlayAgain()
enScoreViewModel
.
<Button
android:id="@+id/play_again_button"
...
android:onClick="@{() -> scoreViewModel.onPlayAgain()}"
... />
- En
ScoreFragment
, dentro deonCreateView()
, inicializa el elementoviewModel
. 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 paraplayAgainButton
. Si Android Studio muestra un error, limpia y vuelve a compilar el proyecto.
Código para quitar:
binding.playAgainButton.setOnClickListener { viewModel.onPlayAgain() }
- Ejecuta la app, que 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 del botón enScoreFragment
.
Solución de 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 compilar la app, por lo que no ves advertencias ni código rojo mientras escribes el código. Sin embargo, en el tiempo de compilación, se producen errores criptográficos 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
, significa que hay un error con la vinculación de datos. - En el archivo en formato XML de diseño, comprueba si hay errores en los atributos
onClick
que usan la 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, revisa la ortografía de la variable de vinculación de datos.
Por ejemplo, ten en cuenta el error de ortografía del nombre de la función onCorrect()
en el siguiente valor de atributo:
android:onClick="@{() -> gameViewModel.onCorrectx()}"
Observa también el error ortográfico de gameViewModel
en la sección <data>
del archivo XML:
<data>
<variable
name="gameViewModelx"
type="com.example.android.guesstheword.screens.game.GameViewModel" />
</data>
Android Studio no detectará errores como estos hasta que compiles la app y, luego, el compilador mostrará 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 vínculos de datos a los objetos ViewModel
, está todo listo para incorporar LiveData
.
En esta tarea, cambiarás la app de GuessTheWord para usar LiveData
como la fuente de vinculación de datos a fin de notificar a la IU sobre los cambios en los datos, sin usar los métodos del observador de LiveData
.
Paso 1: Agrega la palabra LiveData 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
.
Configúralo en el objeto LiveData
, word
desde GameViewModel
, mediante la variable de vinculación gameViewModel
.
<TextView
android:id="@+id/word_text"
...
android:text="@{gameViewModel.word}"
... />
Ten en cuenta que no tienes que usar word.value
. En su lugar, puedes usar el objeto LiveData
real. El objeto LiveData
muestra el valor actual del word
. Si el valor de word
es nulo, el objeto LiveData
muestra una string 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 deLiveData
word
.
Código para 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 está actualizando sin un método de observador en el controlador de IU.
Paso 2: Agrega LiveData de puntuación al archivo Score_fragment.xml
En este paso, vinculas el LiveData
score
a la vista de texto de puntuación en el fragmento de puntuación.
- En
score_fragment.xml
, agrega el atributoandroid:text
a la vista de texto de puntuación. AsignascoreViewModel.score
al atributotext
. Debido a quescore
es un número entero, conviértelo en una string 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 para el objetoscore
.
Código para 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 string con vinculación de datos
En el diseño, puedes agregar formato de strings junto con la vinculación de datos. En esta tarea, darás formato a la palabra actual para agregar comillas alrededor de ella. También debe darle formato a la string de puntuación para que incluya como prefijo la Puntuación actual, como se muestra en la siguiente imagen.
- En
string.xml
, agrega las siguientes strings, que usarás para dar formato a las vistas de textoword
yscore
.%s
y%d
son los marcadores de posición para la palabra y la puntuación actuales.
<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 stringsquote_format
. PasagameViewModel.word
. De esta manera, se pasa la palabra actual como un argumento a la string de formato.
<TextView
android:id="@+id/word_text"
...
android:text="@{@string/quote_format(gameViewModel.word)}"
... />
- Formatea la vista de texto
score
similar aword_text
. Engame_fragment.xml
, agrega el atributotext
a la vista de textoscore_text
. Usa el recurso de stringsscore_format
, que toma un argumento numérico, representado por el marcador de posición%d
. Pasa el objetoLiveData
,score
, como un argumento a esta string 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 para 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 formato 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 ViewModel
, sin usar controladores de clics en el fragmento. También usaste objetos LiveData
como la fuente de vinculación de datos para notificar automáticamente a la IU sobre cambios en los datos, sin los métodos del observador de LiveData
.
Proyecto de Android Studio: GuessTheWord
- La biblioteca de vinculación de datos funciona a la perfección con los componentes de la arquitectura de Android, como
ViewModel
yLiveData
. - Los diseños de tu app pueden vincularse a los datos en los componentes de la arquitectura, que ya te ayudan a administrar el ciclo de vida del controlador de IU y a notificar sobre los cambios en los datos.
Vinculación de datos de ViewModel
- Puedes asociar un
ViewModel
con un diseño mediante 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
.
Cómo asociar un ViewModel
a 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 del 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 de los controladores de IU por las vinculaciones de objetos de escucha del archivo de diseño.
- La vinculación de datos crea un objeto de escucha y lo configura 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
en elViewModel
. Cuando cambiaLiveData
enViewModel
, las vistas del diseño se pueden actualizar automáticamente, sin los métodos del observador en los controladores de IU.
android:text="@{gameViewModel.word}"
- Para hacer que la vinculación de datos de
LiveData
funcione, configura 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
Formato de strings con vinculación de datos
- Con la vinculación de datos, puedes dar formato a un recurso de strings con marcadores de posición como
%s
para strings y%d
para números enteros. - Para actualizar el atributo
text
de la vista, pasa el objetoLiveData
como argumento de la string 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 tareas para los alumnos que trabajan con este codelab como parte de un curso que dicta un instructor. Depende del instructor hacer lo siguiente:
- Si es necesario, asigna la tarea.
- Informa a los alumnos cómo enviar los deberes.
- Califica las tareas.
Los instructores pueden usar estas sugerencias lo poco o lo que quieran, y deben asignar cualquier otra tarea que consideren apropiada.
Si estás trabajando en este codelab por tu cuenta, usa estas tareas para poner a prueba tus conocimientos.
Responde estas preguntas
Pregunta 1
¿Cuál de las siguientes afirmaciones no es verdadera respecto de 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
Supongamos que tu app incluye este recurso de strings:<string name="generic_name">Hello %s</string>
¿Cuál de las siguientes opciones es la sintaxis correcta para aplicar formato a la string 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
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 siguiente lección:
Para ver vínculos a otros codelabs de este curso, consulta la página de destino de codelabs sobre aspectos básicos de Kotlin para Android.