Aspectos básicos de Kotlin para Android 02.4: Aspectos básicos de la vinculación de datos

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 este curso, usaste la función findViewById() para obtener referencias a las vistas. Cuando tu app tiene jerarquías de vistas complejas, findViewById() es costoso y ralentiza la app, ya que Android recorre la jerarquía de vistas, comenzando por la raíz, hasta que encuentra la vista deseada. Afortunadamente, existe una mejor manera.

Para establecer datos en las vistas, usaste recursos de cadena y estableciste los datos desde la actividad. Sería más eficiente si la vista conociera los datos. Y, afortunadamente, esto también es posible.

En este codelab, aprenderás a usar la vinculación de datos para eliminar la necesidad de findViewById(). También aprenderás a usar la vinculación de datos para acceder a los datos directamente desde una vista.

Conocimientos que ya deberías tener

Debes estar familiarizado con lo siguiente:

  • Qué es una actividad y cómo configurar una actividad con un diseño en onCreate()
  • Crear una vista de texto y establecer el texto que muestra la vista de texto
  • Usa findViewById() para obtener una referencia a una vista.
  • Crear y editar un diseño XML básico para una vista

Qué aprenderás

  • Cómo usar la biblioteca de vinculación de datos para eliminar llamadas ineficientes a findViewById()
  • Cómo acceder a los datos de la app directamente desde XML

Actividades

  • Modificar una app para que use la vinculación de datos en lugar de findViewById() y acceder a los datos directamente desde los archivos XML de diseño

En este codelab, comenzarás con la app de AboutMe y la cambiarás para que use la vinculación de datos. Cuando termines, la app se verá exactamente igual.

Esto es lo que hace la app de AboutMe:

  • Cuando el usuario abre la app, esta muestra un nombre, un campo para ingresar un apodo, un botón Listo, una imagen de una estrella y texto desplazable.
  • El usuario puede ingresar un sobrenombre y presionar el botón Listo. El campo editable y el botón se reemplazan por una vista de texto que muestra el sobrenombre ingresado.


Puedes usar el código que compilaste en el codelab anterior o descargar el código AboutMeDataBinding-Starter de GitHub.

El código que escribiste en codelabs anteriores usa la función findViewById() para obtener referencias a las vistas.

Cada vez que usas findViewById() para buscar una vista después de que se crea o se vuelve a crear, el sistema Android desvía la jerarquía de vistas en el tiempo de ejecución para encontrarla. Cuando tu app tiene solo unas pocas vistas, esto no es un problema. Sin embargo, las apps de producción pueden tener docenas de vistas en un diseño y, aun con el mejor diseño, habrá vistas anidadas.

Piensa en un diseño lineal que contenga una vista de desplazamiento que, a su vez, contenga una vista de texto. En el caso de una jerarquía de vistas grande o profunda, encontrar una vista puede llevar tanto tiempo que puede ralentizar notablemente la app para el usuario. Almacenar en caché las vistas en variables puede ayudar, pero aún debes inicializar una variable para cada vista, en cada espacio de nombres. Con muchas vistas y múltiples actividades, esto también se acumula.

Una solución es crear un objeto que contenga una referencia a cada vista. Tu app puede usar este objeto, llamado objeto Binding. Esta técnica se denomina vinculación de datos. Una vez que se crea un objeto de vinculación para tu app, puedes acceder a las vistas y a otros datos a través del objeto de vinculación, sin tener que recorrer la jerarquía de vistas ni buscar los datos.

La vinculación de datos tiene los siguientes beneficios:

  • El código es más corto, más fácil de leer y más fácil de mantener que el código que usa findByView().
  • Los datos y las vistas están claramente separados. Este beneficio de la vinculación de datos se vuelve cada vez más importante más adelante en este curso.
  • El sistema Android solo recorre la jerarquía de vistas una vez para obtener cada vista, y esto sucede durante el inicio de la app, no en el tiempo de ejecución cuando el usuario interactúa con la app.
  • Obtienes seguridad de tipos para acceder a las vistas. (La seguridad de tipos significa que el compilador valida tipos durante la compilación y arroja un error si intentas asignar el tipo incorrecto a una variable).

En esta tarea, configurarás la vinculación de datos y la usarás para reemplazar las llamadas a findViewById() por llamadas al objeto de vinculación.

Paso 1: Habilita la vinculación de datos

Para usar la vinculación de datos, debes habilitarla en tu archivo Gradle, ya que no está habilitada de forma predeterminada. Esto se debe a que la vinculación de datos aumenta el tiempo de compilación y puede afectar el tiempo de inicio de la app.

  1. Si no tienes la app de AboutMe de un codelab anterior, obtén el código de AboutMeDataBinding-Starter de GitHub. Ábrelo en Android Studio.
  2. Abre el archivo build.gradle (Module: app).
  3. Dentro de la sección android, antes de la llave de cierre, agrega una sección dataBinding y establece enabled en true.
dataBinding {
    enabled = true
}
  1. Cuando se te solicite, sincroniza el proyecto. Si no se te solicita, selecciona File > Sync Project with Gradle Files.
  2. Puedes ejecutar la app, pero no verás ningún cambio.

Paso 2: Cambia el archivo de diseño para que se pueda usar con la vinculación de datos

Para trabajar con la vinculación de datos, debes incluir tu diseño XML en una etiqueta <layout>. Esto se debe a que la clase raíz ya no es un grupo de vistas, sino un diseño que contiene grupos de vistas y vistas. Luego, el objeto de vinculación puede conocer el diseño y las vistas que contiene.

  1. Abre el archivo activity_main.xml.
  2. Cambia a la pestaña Texto.
  3. Agrega <layout></layout> como la etiqueta más externa alrededor de <LinearLayout>.
<layout>
   <LinearLayout ... >
   ...
   </LinearLayout>
</layout>
  1. Elige Code > Reformat code para corregir la sangría del código.

    Las declaraciones de espacio de nombres para un diseño deben estar en la etiqueta más externa.
  1. Corta las declaraciones de espacio de nombres de <LinearLayout> y pégalas en la etiqueta <layout>. La etiqueta <layout> de apertura debería verse como se muestra a continuación, y la etiqueta <LinearLayout> solo debería contener propiedades de vista.
<layout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto">
  1. Compila y ejecuta tu app para verificar que lo hiciste correctamente.

Paso 3: Crea un objeto de vinculación en la actividad principal

Agrega una referencia al objeto de vinculación en la actividad principal para que puedas usarla para acceder a las vistas:

  1. Abre el archivo MainActivity.kt.
  2. Antes de onCreate(), en el nivel superior, crea una variable para el objeto de vinculación. Por lo general, esta variable se denomina binding.

    El compilador crea el tipo de binding, la clase ActivityMainBinding, específicamente para esta actividad principal. El nombre se deriva del nombre del archivo de diseño, es decir, activity_main + Binding.
private lateinit var binding: ActivityMainBinding
  1. Si Android Studio lo solicita, importa ActivityMainBinding. Si no se te solicita, haz clic en ActivityMainBinding y presiona Alt+Enter (Option+Enter en Mac) para importar esta clase faltante. (Para obtener más combinaciones de teclas, consulta Combinaciones de teclas).

    La instrucción import debería ser similar a la que se muestra a continuación.
import com.example.android.aboutme.databinding.ActivityMainBinding

A continuación, reemplaza la función setContentView() actual por una instrucción que haga lo siguiente:

  • Crea el objeto de vinculación.
  • Usa la función setContentView() de la clase DataBindingUtil para asociar el diseño activity_main con el MainActivity. Esta función setContentView() también se encarga de configurar el enlace de datos para las vistas.
  1. En onCreate(), reemplaza la llamada a setContentView() por la siguiente línea de código.
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
  1. Importa el elemento DataBindingUtil.
import androidx.databinding.DataBindingUtil

Paso 4: Usa el objeto de vinculación para reemplazar todas las llamadas a findViewById()

Ahora puedes reemplazar todas las llamadas a findViewById() por referencias a las vistas que se encuentran en el objeto de vinculación. Cuando se crea el objeto de vinculación, el compilador genera los nombres de las vistas en el objeto de vinculación a partir de los IDs de las vistas en el diseño y los convierte al formato de mayúsculas mediales. Por ejemplo, done_button es doneButton en el objeto de vinculación, nickname_edit se convierte en nicknameEdit y nickname_text se convierte en nicknameText.

  1. En onCreate(), reemplaza el código que usa findViewById() para encontrar el done_button por el código que hace referencia al botón en el objeto de vinculación.

    Reemplaza este código: findViewById<Button>(R.id.done_button)
    por: binding.doneButton

    El código finalizado para establecer el objeto de escucha de clics en onCreate() debería verse de la siguiente manera.
binding.doneButton.setOnClickListener {
   addNickname(it)
}
  1. Haz lo mismo para todas las llamadas a findViewById() en la función addNickname().
    Reemplaza todos los casos de findViewById<View>(R.id.id_view) por binding.idView. Sigue estos pasos:
  • Borra las definiciones de las variables editText y nicknameTextView junto con sus llamadas a findViewById(). Esto generará errores.
  • Para corregir los errores, obtén las vistas nicknameText, nicknameEdit y doneButton del objeto binding en lugar de las variables (borradas).
  • Reemplaza view.visibility por binding.doneButton.visibility. Usar binding.doneButton en lugar del view pasado hace que el código sea más coherente.

    El resultado es el siguiente código:
binding.nicknameText.text = binding.nicknameEdit.text
binding.nicknameEdit.visibility = View.GONE
binding.doneButton.visibility = View.GONE
binding.nicknameText.visibility = View.VISIBLE
  • No hay cambios en la funcionalidad. De manera opcional, ahora puedes eliminar el parámetro view y actualizar todos los usos de view para que usen binding.doneButton dentro de esta función.
  1. El nicknameText requiere un String, y nicknameEdit.text es un Editable. Cuando se usa la vinculación de datos, es necesario convertir de forma explícita el Editable en un String.
binding.nicknameText.text = binding.nicknameEdit.text.toString()
  1. Puedes borrar las importaciones atenuadas.
  2. Convierte la función a Kotlin con apply{}.
binding.apply {
   nicknameText.text = nicknameEdit.text.toString()
   nicknameEdit.visibility = View.GONE
   doneButton.visibility = View.GONE
   nicknameText.visibility = View.VISIBLE
}
  1. Compila y ejecuta tu app, que debería verse y funcionar exactamente igual que antes.

Puedes aprovechar la vinculación de datos para que una clase de datos esté disponible directamente para una vista. Esta técnica simplifica el código y es muy valiosa para controlar casos más complejos.

En este ejemplo, en lugar de configurar el nombre y el apodo con recursos de cadena, crearás una clase de datos para el nombre y el apodo. Haces que la clase de datos esté disponible para la vista con la vinculación de datos.

Paso 1: Crea la clase de datos MyName

  1. En Android Studio, en el directorio java, abre el archivo MyName.kt. Si no tienes este archivo, crea uno nuevo de Kotlin y llámalo MyName.kt.
  2. Define una clase de datos para el nombre y el apodo. Usa cadenas vacías como valores predeterminados.
data class MyName(var name: String = "", var nickname: String = "")

Paso 2: Agrega datos al diseño

En el archivo activity_main.xml, el nombre se establece actualmente en un TextView a partir de un recurso de cadena. Debes reemplazar la referencia al nombre por una referencia a los datos de la clase de datos.

  1. Abre activity_main.xml en la pestaña Text.
  2. En la parte superior del diseño, entre las etiquetas <layout> y <LinearLayout>, inserta una etiqueta <data></data>. Aquí es donde conectarás la vista con los datos.
<data>
  
</data>

Dentro de las etiquetas de datos, puedes declarar variables con nombre que contengan una referencia a una clase.

  1. Dentro de la etiqueta <data>, agrega una etiqueta <variable>.
  2. Agrega un parámetro name para darle a la variable el nombre "myName". Agrega un parámetro type y configura el tipo como un nombre completamente calificado de la clase de datos MyName (nombre del paquete + nombre de la variable).
<variable
       name="myName"
       type="com.example.android.aboutme.MyName" />

Ahora, en lugar de usar el recurso de cadena para el nombre, puedes hacer referencia a la variable myName.

  1. Reemplaza android:text="@string/name" por el siguiente código.

@={} es una directiva para obtener los datos a los que se hace referencia dentro de las llaves.

myName hace referencia a la variable myName que definiste anteriormente, que apunta a la clase de datos myName y recupera la propiedad name de la clase.

android:text="@={myName.name}"

Paso 3: Crea los datos

Ahora tienes una referencia a los datos en tu archivo de diseño. A continuación, crearás los datos reales.

  1. Abre el archivo MainActivity.kt.
  2. Arriba de onCreate(), crea una variable privada, también llamada myName por convención. Asigna a la variable una instancia de la clase de datos MyName y pasa el nombre.
private val myName: MyName = MyName("Aleks Haecky")
  1. En onCreate(), establece el valor de la variable myName en el archivo de diseño como el valor de la variable myName que acabas de declarar. No puedes acceder a la variable directamente en el XML. Debes acceder a él a través del objeto de vinculación.
binding.myName = myName
  1. Es posible que se muestre un error, ya que debes actualizar el objeto de vinculación después de realizar cambios. Compila tu app y el error debería desaparecer.

Paso 4: Usa la clase de datos para el apodo en el TextView

El paso final es usar también la clase de datos para el apodo en TextView.

  1. Abre activity_main.xml.
  2. En la vista de texto nickname_text, agrega una propiedad text. Haz referencia a nickname en la clase de datos, como se muestra a continuación.
android:text="@={myName.nickname}"
  1. En ActivityMain, reemplaza
    nicknameText.text = nicknameEdit.text.toString()
    por código para establecer el apodo en la variable myName.
myName?.nickname = nicknameEdit.text.toString()

Después de establecer el apodo, querrás que tu código actualice la IU con los datos nuevos. Para ello, debes invalidar todas las expresiones de vinculación para que se vuelvan a crear con los datos correctos.

  1. Agrega invalidateAll() después de configurar el apodo para que la IU se actualice con el valor del objeto de vinculación actualizado.
binding.apply {
   myName?.nickname = nicknameEdit.text.toString()
   invalidateAll()
   ...
}
  1. Compila y ejecuta tu app, y debería funcionar exactamente igual que antes.

Proyecto de Android Studio: AboutMeDataBinding

Pasos para usar la vinculación de datos y reemplazar las llamadas a findViewById():

  1. Habilita la vinculación de datos en la sección android del archivo build.gradle:
    dataBinding { enabled = true }
  2. Usa <layout> como la vista raíz en tu diseño XML.
  3. Define una variable de vinculación:
    private lateinit var binding: ActivityMainBinding
  4. Crea un objeto de vinculación en MainActivity y reemplaza setContentView:
    binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
  5. Reemplaza las llamadas a findViewById() por referencias a la vista en el objeto de vinculación. Por ejemplo:
    findViewById<Button>(R.id.done_button) ⇒ binding.doneButton
    (en el ejemplo, el nombre de la vista se genera en formato de mayúsculas mediales a partir del id de la vista en el XML).

Pasos para vincular vistas a datos:

  1. Crea una clase de datos para tus datos.
  2. Agrega un bloque <data> dentro de la etiqueta <layout>.
  3. Define un <variable> con un nombre y un tipo que sea la clase de datos.
<data>
   <variable
       name="myName"
       type="com.example.android.aboutme.MyName" />
</data>
  1. En MainActivity, crea una variable con una instancia de la clase de datos. Por ejemplo:
    private val myName: MyName = MyName("Aleks Haecky")
  1. En el objeto de vinculación, establece la variable en la que acabas de crear:
    binding.myName = myName
  1. En el XML, configura el contenido de la vista en la variable que definiste en el bloque <data>. Usa la notación de punto para acceder a los datos dentro de la clase de datos.
    android:text="@={myName.name}"

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

¿Por qué quieres minimizar las llamadas explícitas e implícitas a findViewById()?

  • Cada vez que se llama a findViewById(), desvía la jerarquía de vistas.
  • findViewById() se ejecuta en el subproceso de IU o en el principal.
  • Estas llamadas pueden ralentizar la interfaz del usuario.
  • Es menos probable que falle la app.

Pregunta 2

¿Cómo describirías la vinculación de datos?

Por ejemplo, estas son algunas cosas que podrías decir sobre la vinculación de datos:

  • La idea principal de la vinculación de datos es crear un objeto que conecte, asigne o vincule dos fragmentos de información distantes en tiempo de compilación, de modo que no tengas que buscar los datos en tiempo de ejecución.
  • El objeto que te muestra estas vinculaciones se denomina objeto de vinculación.
  • El objeto de vinculación lo crea el compilador.

Pregunta 3

¿Cuál de las siguientes opciones NO es un beneficio de la vinculación de datos?

  • El código es más corto y más fácil de leer y mantener.
  • Los datos y las vistas están claramente separados.
  • El sistema Android solo atraviesa la jerarquía de vistas una vez para obtener cada vista.
  • Llamar a findViewById() genera un error de compilación.
  • Seguridad de tipos para acceder a las vistas

Pregunta 4

¿Cuál es la función de la etiqueta <layout>?

  • Lo envuelves alrededor de tu vista raíz en el diseño.
  • Se crean vinculaciones para todas las vistas de un diseño.
  • Designa la vista de nivel superior en un diseño XML que usa vinculación de datos.
  • Puedes usar la etiqueta <data> dentro de un <layout> para vincular una variable a una clase de datos.

Pregunta 5

¿Cuál es la forma correcta de hacer referencia a datos vinculados en el diseño XML?

  • android:text="@={myDataClass.property}"
  • android:text="@={myDataClass}"
  • android:text="@={myDataClass.property.toString()}"
  • android:text="@={myDataClass.bound_data.property}"

Comienza la próxima lección: 3.1: Cómo crear un fragmento

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.