Funciones Ninja en Kotlin. Entendiendo la potencia de los genéricos (KDA 12)
Antonio Leiva

El uso simultáneo de varias funcionalidades de Kotlin unidas al uso de genéricos permiten crear funciones que nos simplificarán mucho el código, mientras mantenemos su legibilidad.

Hay varias funciones incluidas en la librería de Kotlin que son realmente útiles, pero que una vez dominados algunos conceptos son muy sencillas de utilizar.

Si quieres empezar hoy, te recomiendo que le eches un vistazo a mi training gratuito, donde tendrás una hora y media de contenido para saber cuáles son tus siguientes pasos a dar para convertirte en un experto en Kotlin.

Función with

Aunque existen varias funciones similares, me voy a centrar en with para destriparla.

¿Qué nos permite esta función? Con ella podemos hacer bloques de código en los que no tengamos que repetir innecesariamente la misma variable x veces.

Pueden venir a sustituir a los builders, sin necesidad de crear uno específico para cada clase.

Por ejemplo, volviendo al caso del ViewGroup que teníamos en el artículo anterior, podríamos convertir este código:

val childViews = (0..viewGroup.childCount - 1).map { viewGroup.getChildAt(it) }

en esto:

with(viewGroup) {
    val childViews = (0..childCount - 1).map { getChildAt(it) }
}

Como ves, el código dentro de los corchetes se comporta como si fuera código dentro de la propia clase.

¿Cómo conseguimos esto? Ya lo hemos visto anteriormente: con una función de extensión.

Funciones de extensión como parámetros

Ya estamos rizando el rizo, pero esto es tan útil que necesitas conocerlo.

Puedes definir una función de extensión como parámetro de otra función.

¿Cómo implementarías la función with para poder ejecutar el ejemplo anterior? Lo más sencillo sería esto:

inline fun with(view: ViewGroup, f: ViewGroup.() -> Unit) {
    view.f()
}

Como ves, recibe un ViewGroup como parámetro, y una función de extensión sobre ViewGroup. Nada limita por tanto que ese ViewGroup ejecute esa función.

Pero esto es muy poco flexible. ¿Vamos a necesitar una función similar para cada tipo de datos?

Por supuesto que no.

Tipos genéricos

Podemos hacer la función anterior genérica de forma muy sencilla. Tan solo hay que sustituir ViewGroup por T:

inline fun  with(obj: T, f: T.() -> Unit) {
    obj.f()
}

Ahora ya es útil para cualquier tipo. Un ejemplo:

with(textView) {
    text = "Hello World"
    visibility = View.VISIBLE
    textSize = sp(14).toFloat()
}

Pero aquí nos estamos perdiendo un potencial importante que hablábamos al principio: el de actuar como builder.

Devolver un valor de tipo genérico

Si queremos que actúe como un builder de verdad, necesitamos que el valor construido se devuelva de alguna forma:

inline fun  with(obj: T, f: T.() -> Unit): T {
    obj.f()
    return obj
}

De esta forma, nuestro código quedaría así:

val textView = with(TextView(this)) {
    text = "Hello World"
    visibility = View.VISIBLE
    textSize = sp(14).toFloat()
}

sp() es una función de la librería de Anko, de la que ya hemos hablado en alguna ocasión en esta serie.

Si te fijas en la definición oficial de la función, es muy parecida a lo que hemos hecho:

public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()

La principal diferencia es que la función de extensión que se pasa por parámetro retorna un valor que puede ser diferente del que se está pasando por parámetro.

Para conseguir el resultado anterior necesitaríamos hacer lo siguiente:

val textView = with(TextView(this)) {
    text = "Hello World"
    visibility = View.VISIBLE
    textSize = sp(14).toFloat()
    this
}

La última línea implica que se devolverá el objeto que está ejecutando la función de extensión.

Otras funciones interesantes

Hay una función que funciona muy parecido a lo que queríamos conseguir en el apartado anterior, y que se llama apply.

apply

En vez de pasarse el objeto por parámetro, esta función actúa como función de extensión de dicho objeto:

val textView = TextView(this).apply {
    text = "Hello World"
    visibility = View.VISIBLE
    textSize = sp(14).toFloat()
}

let

Otra función muy útil es let, que yo la encuentro particularmente útil para tratar con null encadenados.

Si el objeto correspondiente no es nulo, ejecutará el contenido dentro de la función de extensión:

textView?.text?.let { toast(it) }

El siguiente texto sólo se mostrará en un toast si el TextView no es null.

Conclusión

Utilizando la potencia de los tipos genéricos con funciones de extensión, podemos hacer cosas muy interesantes.

Te animo a que crees tus propias funciones que te permitan simplificar la tarea.

Si todo esto te apasiona tanto como a mí, te animo a que te apuntes a mi training gratuito donde te contaré todo lo que necesitas para aprender a crear tus Apps Android en Kotlin desde cero.

Quizá también te interese…

Cómo crear un backend en Kotlin usando Ktor

Cómo crear un backend en Kotlin usando Ktor

Ktor es un framework de servidor web ligero y rápido para Kotlin, desarrollado por JetBrains. Es ideal para crear aplicaciones web y servicios RESTful, y es muy fácil de usar y configurar. En este artículo, vamos a ver cómo crear un backend para una aplicación de...

Cómo simular una base de datos reactiva en Room con Fakes

Cómo simular una base de datos reactiva en Room con Fakes

En el desarrollo de aplicaciones móviles es muy común utilizar bases de datos para almacenar y gestionar la información que se utiliza en la aplicación. En el caso de Android, una de las opciones más populares es Room, una librería de persistencia de datos que...

Flows de Kotlin para implementar búsquedas en tiempo real

Flows de Kotlin para implementar búsquedas en tiempo real

En Android, los Flows de Kotlin son una manera de representar secuencias de datos asincrónicas que emiten valores de forma continua. Estos Flows pueden ser útiles en situaciones en las que deseamos escuchar eventos y procesar los resultados de forma asíncrona, como en...

0 comentarios

Enviar un comentario

Los datos personales que proporciones a través de este formulario quedarán registrados en un fichero de DevExpert, S.L.U., con el fin de gestionar los comentarios que realizas en este blog. La legitimación se realiza a través del consentimiento de la parte interesada. Si no se acepta, no podrás comentar en este blog. Los datos que proporciona solo se utilizan para evitar el correo no deseado y no se usarán para nada más. Puede ejercer los derechos de acceso, rectificación, cancelación y oposición en contacto@devexperto.com.

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Acepto la política de privacidad *