Data Binding para mostrar datos

Data Binding para mostrar datos en Kotlin

En la entrada anterior (Fundamentos de Android: Data Binding) vimos cómo utilizar Data Binding para acceder a los elementos del diseño desde la actividad. Pero además podemos usar data binding para mostrar datos haciendo que los objetos de una data class (ver Clase de datos o data class en Kotlin) estén directamente disponibles para un elemento del diseño.

Para verlo con un ejemplo vamos a continuar con el proyecto ControlAcceso como lo dejamos en la entrada sobre data binding.

Vemos que en el archivo activity_login_check.xml el TextView login_txt muesra el texto desde un recurso de texto (@string/bienvenido) que luego es cambiado por la actividad (binding.loginTxt.text = getString(R.string.bienvenido, user)) con el dato recuperado que se ha enviado desde la primera actividad con la clase Intent. Nuestro objetivo será que la segunda actividad almacene en la clase Usuario el dato recibido y que después el TextView del diseño remplace el recurso de texto por una referencia al valor almacenado en la data class.

Primero crearemos una data class para el nombre de usuario y la contraseña en un nuevo archivo llamado Usuario.kt en el directorio java (sobre java/com/android/controlacceso -> New -> Kotlin File/Class):
data class Usuario(var nombre: String = "", var pass: String = "")

Para añadir los datos al diseño, abrimos activity_login_check.xml en modo Text, e insertamos la etiqueta <data> </data> en la parte superior del diseño entre las etiquetas <layout> y <LinearLayout>:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">

    <data>

    </data>

    <LinearLayout

Dentro de las etiquetas <data> podemos declarar variables con un nombre y un parámetro de tipo con la referencia completa a la clase:
<data>
    <variable
            name="usuario"
            type="com.android.controlacceso.Usuario" />
</data>

Ahora en lugar de utilizar el recurso de texto para el TextView, podemos hacer referencia a la variable usuario. Así, donde tenemos
android:text="@string/bienvenido"
escribimos en su lugar:
android:text="@={usuario.nombre}"
Donde la directiva @={} sirve para obtener los datos a los que se hace referencia dentro de las llaves, y usuario hace referencia a la variable que definimos antes con el mismo nombre (que apunta a la clase Usuario) y obtiene el valor de la propiedad nombre de esa clase.


En el archivo LoginCheck.kt, antes de onCreate, creamos una variable privada de la clase Usuario:
private lateinit var usuario: Usuario

Una vez que la segunda actividad recibe el valor desde la primera actividad, le asignamos a la variable usuario una instancia de la clase Usuario con ese valor:
usuario = Usuario(user.toString())

Y utilizando el objeto de enlace (binding object) asignamos el valor de esta variable a la variable del mismo nombre que hemos creado en el archivo de diseño:
binding.usuario = usuario

Y por tanto ya no necesitamos ajustar el valor del TextView desde la actividad puesto que es accesible directamente desde el diseño:
// binding.loginTxt.text = getString(R.string.bienvenido, user)

Esto puede mostrar un error porque se necesita actualizar el objeto de enlace después de realizar estos cambios; intenta resolverlo actualizando los archivos generados desde Build -> Clean Project seguido de Build -> Rebuild Project.

Ejecutamos en el emulador para comprobar que funciona.

También podríamos hacer que el usuario pudiera ver la contraseña introducida una vez que ha iniciado sesión. Para ello, repitiendo los pasos anteriores, debemos:
  1. Enviar el valor de la contraseña desde la primera a la segunda actividad.
  2. Vincular el valor de una view a la variable del diseño, y concretamente a la propiedad de la clase que nos interesa.
  3. En la actividad, crear una instancia de la clase Usuario con los valores recibidos, y almacenarla en una variable (y vincular su valor a la variable de diseño).

Para la primera tarea, utilizamos la clase Bundle para enviar varios datos a la vez:
val intento1 = Intent(this, LoginCheck::class.java)
val bundle = Bundle()
bundle.putString("usuario", binding.userInput.text.toString())
bundle.putString("pass", binding.passInput.text.toString())
intento1.putExtra("datosUsuario", bundle)
startActivity(intento1)

En el archivo de diseño activity_login_check.xml ya tenemos creada una variable que apunta a la clase Usuario de la data class. Entonces en el mismo archivo, creamos un nuevo elemento (en este caso un EditText) y vinculamos su valor a esa variable (a la propiedad pass de la clase Usuario). Además junto a él creamos un CheckBox para mostrar / ocultar la contraseña. Ambos elementos contenidos en un LinearLayout horizontal sobre el botón CERRAR:
<LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

    <EditText
            android:id="@+id/pass_txt"
            style="@style/estilo_texto"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:background="@null"
            android:ems="10"
            android:enabled="false"
            android:inputType="textPassword"
            android:text="@={usuario.pass}"
            android:textAlignment="center" />

    <CheckBox
            android:id="@+id/checkBox"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="@string/btn_ver" />

</LinearLayout>

Observa que en el EditText hemos ajustado el atributo background a null para que no muestre la línea inferior característica de los elementos editables, el atributo enabled a false para que no sea editable por el usuario y el atributo inputType a textPassword para que por defecto no muestre el texto de la contraseña.

En la actividad correspondiente (LoginCheck), recibimos los datos desde la primera actividad y con ellos creamos una instancia de la clase usuario que almacenamos en la variable declarada previamente:
val bundle = intent.getBundleExtra("datosUsuario")
val user = bundle?.getString("usuario").toString()
val pass = bundle?.getString("pass").toString()

usuario = Usuario(user, pass)

Además, como ya teníamos, a través del objeto de enlace asignamos a la variable del diseño los valores de la instancia de clase:
binding.usuario = usuario

Por último, añadimos un controlador para el CheckBox para que cambie el tipo de datos introducidos en el EditText en función de si está o no activado:
binding.checkBox.setOnClickListener(View.OnClickListener {
    if (binding.checkBox.isChecked) {
        binding.passTxt.inputType = InputType.TYPE_CLASS_TEXT
    } else {
        binding.passTxt.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
    }
})



A continuación se muestran los códigos de los archivos de las actividades y de los diseños:
// MainActivity.kt
package com.android.controlacceso

import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import com.android.controlacceso.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

        binding.entrarBtn.setOnClickListener {
            if (!esNuloVacio(binding.userInput.text.toString()) && !esNuloVacio(binding.passInput.text.toString())) {
                val intento1 = Intent(this, LoginCheck::class.java)
                val bundle = Bundle()
                bundle.putString("usuario", binding.userInput.text.toString())
                bundle.putString("pass", binding.passInput.text.toString())
                intento1.putExtra("datosUsuario", bundle)
                binding.apply {
                    userInput.setText("")
                    passInput.setText("")
                }
                startActivity(intento1)
            } else {
                Toast.makeText(
                    this, "ERROR: Usuario y contraseña requeridos.",
                    Toast.LENGTH_SHORT
                ).show()
            }
        }
    }

    private fun esNuloVacio(str: String?): Boolean {
        return !(str != null && !str.trim().isEmpty())
    }
}
<!--- activity_main.xml -->
<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">

    <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            tools:context=".MainActivity">

        <LinearLayout
                android:id="@+id/linearLayout"
                style="@style/estilo_linear"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

            <TextView
                    android:id="@+id/user_txt"
                    style="@style/estilo_texto"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginEnd="@dimen/margen_txt_end1"
                    android:text="@string/usuario" />

            <EditText
                    android:id="@+id/user_input"
                    style="@style/estilo_texto"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:ems="10"
                    android:hint="@string/usuario"
                    android:inputType="textPersonName" />
        </LinearLayout>

        <LinearLayout
                android:id="@+id/linearLayout2"
                style="@style/estilo_linear"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center_horizontal"
                android:orientation="horizontal">

            <TextView
                    android:id="@+id/pass_txt"
                    style="@style/estilo_texto"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginEnd="@dimen/margen_txt_end2"
                    android:text="@string/contraseña" />

            <EditText
                    android:id="@+id/pass_input"
                    style="@style/estilo_texto"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:ems="10"
                    android:hint="@string/contraseña"
                    android:inputType="textPassword" />
        </LinearLayout>

        <Button
                android:id="@+id/entrar_btn"
                style="@style/Widget.AppCompat.Button.Colored"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:layout_marginTop="@dimen/margen_linea"
                android:text="@string/btn_entrar" />

    </LinearLayout>

</layout>
// LoginCheck.kt
package com.android.controlacceso

import android.os.Bundle
import android.text.InputType
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import com.android.controlacceso.databinding.ActivityLoginCheckBinding

class LoginCheck : AppCompatActivity() {

    private lateinit var binding: ActivityLoginCheckBinding

    private lateinit var usuario: Usuario

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_login_check)

        val bundle = intent.getBundleExtra("datosUsuario")
        val user = bundle?.getString("usuario").toString()
        val pass = bundle?.getString("pass").toString()

        usuario = Usuario(user, pass)
        binding.usuario = usuario

        binding.checkBox.setOnClickListener(View.OnClickListener {
            if (binding.checkBox.isChecked) {
                binding.passTxt.inputType = InputType.TYPE_CLASS_TEXT
            } else {
                binding.passTxt.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
            }
        })

        binding.buttonCerrar.setOnClickListener {
            finish()
        }
    }
}
<!--- activity_login_check.xml -->
<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
                name="usuario"
                type="com.android.controlacceso.Usuario" />
    </data>

    <LinearLayout
            style="@style/estilo_linear"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            tools:context=".LoginCheck">

        <TextView
                android:id="@+id/acceso_txt"
                style="@style/estilo_texto"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/margen_linea"
                android:text="@string/login_check"
                android:textAlignment="center" />

        <TextView
                android:id="@+id/login_txt"
                style="@style/estilo_texto"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/margen_linea"
                android:text="@={usuario.nombre}"
                android:textAlignment="center" />

        <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal">

            <EditText
                    android:id="@+id/pass_txt"
                    style="@style/estilo_texto"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:background="@null"
                    android:ems="10"
                    android:enabled="false"
                    android:inputType="textPassword"
                    android:text="@={usuario.pass}"
                    android:textAlignment="center" />

            <CheckBox
                    android:id="@+id/checkBox"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="@string/btn_ver" />

        </LinearLayout>

        <Button
                android:id="@+id/button_cerrar"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/margen_linea"
                android:text="@string/cerrar" />

    </LinearLayout>

</layout>

Comentarios

Entradas populares

Recursos gratis para aprender Kotlin

I/O: entrada y salida de datos en consola

Lectura y escritura de archivos