Clases de datos en Kotlin

video[Tutorial Clases de datos en Kotlin

Posiblemente estés acostumbrado a utilizar clases POJO en tus proyectos. Son clases muy simples donde se describe la información necesaria para representar un objeto, pero que entran en interacciones con otras clases o requieren de API externos.
Por lo general, un POJO solo nos proporciona una colección de campos. Aunque también podemos añadir funciones que solo involucren objetos de esta clase. Por ejemplo, la clase PuntoGeografico, podría tener como campos longitud y latitud y una función que nos calcule la distancia entre dos puntos.
Kotlin nos ofrece las clases de datos para poder crear rápidamente POJO. Veamos un ejemplo:

data class Complejo(var real: Double, 
                    var imaginario: Double=0.0) 

Toda clase de datos incorpora las siguientes funciones:

     equals(): compara dos objetos.
       hashCode(): código hash de un objeto usado por el método anterior.
     copy(): copia un objeto.
      toString(): convierte objeto a string.
       component1(), component2(), …: componentes en orden de declaración.

Si una propiedad es declarada en el cuerpo de la clase, en lugar de en el constructor primario, ya no tendrá los beneficos indicados:
 

data class Complejo(var real: Double, 
                    var imaginario: Double=0.0) {
    val irracional: Boolean = false
} 

En en el ejemplo anterior la propiedad irracional es ignorada en las funciones equals(), hashCode(), copy(), toString() y componentX().
Las clases de datos deben cumplir los siguientes requisitos:

  •   El constructor primario ha de tener al menos un parámetro.
  •   Todos los parámetros del constructor principal deben marcarse como val o var.
  •   Las clases de datos no pueden ser abstractas, abiertas, selladas o internas

Al copiar un objeto podemos cambiar algunas de sus propiedades:

val c = Complejo(1.0, -1.0)
val c2 = c.copy(im = 0.0) 

Declaraciones desestructuradas

Desestructurar es el procedimiento por el cual podremos extraer múltiples valo-res que se encuentran almacenados en objetos y vectores. En determinadas ocasiones es necesario desestructurar un objeto en diferentes variables. Se realiza de la siguiente manera:
val c = Complejo(1.0, -1.0); 
val (re, im) = c 
De esta forma podremos acceder a cada uno de los valores de una clase de datos utilizando una sola asignación. Para acceder a cada propiedad se definen las funciones component1(), component2(), … El código de la izquierda es compilado a uno similar al de la derecha.
val (re, im) = c 
val re = c.component1()
val im = c.component2() 
Si algún valor no nos iteresa usamos _ :
val (_, im) = c 
val im = c.component2()  
Podremos utilizar la desestructuración para devolver varios valores de una función:
data class Resultado(val res1: String, val res2: Int)

fun funcion(): Resultado {
    val res1 = "texto"
    val res2 = 3
    return Resultado(res1, res2)
}
// Para usar la función
val (res1, res2) = funcion()  
Primero definimos una clase de datos que tenga como atributos los tipos a devolver, de forma que la función ha de devolver esta clase.
Otra de las utilizaciones más comunes de la desestructuración es para descomponer objetos almacenados en un Map por su identificador clave y valor.
val mapa = HashMap<String, Int>()
for ((clave, valor) in mapa) { … } 

Ejercicio: Migrando la clase Libro de Audiolibros.

A lo largo de esta unidad vamos a migrar el proyecto Audiolibros desarrollado en las unidades 1 y 2 desde Java a Kotlin. En este ejercicio empezamos convirtiendo el POJO Libros en una clase de datos Kotlin.

1. Haz una copia de la carpeta del proyecto Audiolibros y llámala AudiolibrosKotlin.
    
2. Selecciona el menú Tools / Kotlin / Configure Kotlin in Project. Selecciona Android with Gradle y luego los siguientes valores:
3. Abre la clase Libro y selecciona Code / Convert Java File to Kotlin File.
4. Ejecuta el proyecto y verifica que funciona correctamente.
5. La transformación no es todo lo buena que podríamos desear. Vamos a mejorar algunas partes del código. Primero reemplaza class Libro por data class Libro. De esta forma tendremos funciones adicionales para copiar libros, recorrer sus campos, etc.
6. Reemplaza var por val en todos los campos menos novedad y leído. Estos campos no queremos que cambien una vez inicializado un libro.
7. Los campos novedad y leído. los marca como de tipo Boolean?. Puedes eliminar el ?. Este concepto se explica más adelante. Reemplaza los valores por defecto por los siguientes:
data class Libro(val titulo: String,
                 val autor: String, 
                 val urlImagen: String,
                 val urlAudio: String,
                 val genero: String,          // Género literario
                 var novedad: Boolean = true, // Es una novedad
                 var leido: Boolean = false   // Leído por el usuario 
8. Ejecuta el proyecto y verifica que funciona correctamente.
9. El concepto de compation object será explicado más adelante. De momento, mueve el resto de constantes al principio (entre import y class). De esta forma se convierten en constantes de paquete en lugar de clase. Antepón const a las constantes para indicar que su valor ya es conocido en tiempo de compilación.
10. Mueve la función ejemploLibros() tras las constantes. Así será una función estática de paquete en lugar de una función.
11. En el resto del proyecto habrá que cambiar la forma de acceder a las constantes y la función. Por ejemplo, en lugar de escribir Libros.compa-nion.ejemploLibros() directamente escribiremos ejemploLibros(). Sabrás dónde has de hacerlo al aparecer errores de compilación.
12. Puedes eliminar el compation object. Ya no es utilizado.
13. Ejecuta el proyecto y verifica que funciona correctamente.
 

Ejercicio: Migrando la clase MainActivity de Audiolibros.

Vamos a realizar el mismo proceso que en el ejercicio anterior, pero ahora con una actividad:
1. Abre la clase MainActivity y selecciona Code / Convert Java File to Kotlin File. Tras la operación es posible que tengas que añadir algún import adicional.
2. Ejecuta el proyecto y verifica que funciona correctamente.
3. Una de las ventajas de Kotlin a la hora de trabajar con actividades es que ya no va a ser necesario usar findViewById para acceder a las diferentes vistas del layout. Para poder usar esta facilidad, abre build.gradle (Module:app) y añade la línea subrayada:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' 
4. Elimina las siguientes líneas:
private var tabs: TabLayout? = null
…
override fun onCreate(savedInstanceState: Bundle?) {
   …
   tabs = findViewById<View>(R.id.tabs) as TabLayout
Este trabajo es realizado de forma automática por el sistema. Has de tener en cuenta que el nombre del objeto creado coincide con el id declarado en el layout.
5.  Ejecuta el proyecto y verifica que funciona correctamente.
6.  Repite la misma operación para el resto de Vistas del Layout.
7.  Ten en cuenta que alguna vista (como drawer) tiene un id en el layout dife-rente (drawer_layout). En tal caso, tendrás que cambiar uno de los dos nombres.
8.  En la función onNavigationItemSelected reemplaza la lista de if else por una sentencia when.
9.  Busca otros cambios que puedan resultar convenientes.