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.
0 comentarios