Extensiones en Kotlin

Como te hablé en mi post sobre por qué empecé a usar Kotlin, una de las funcionalidades más útiles que nos provee Kotlin es la habilidad de crear extensiones a clases de las cuáles no necesitamos tener acceso a su código. Lo vas a entender mejor con un ejemplo.

Imagina el siguiente helper escrito en Java

public static boolean isWeekend(Date date) {  
   Calendar calendar = Calendar.getInstance();
   calendar.setTime(date);
   int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
   return dayOfWeek == Calendar.SATURDAY || dayOfWeek == Calendar.SUNDAY
}

Es una función estática pura que recibe como parámetro la fecha en la que queremos comprobar si es fin de semana o no. En Java, este método estático tiene que estar dentro de una clase, vamos a suponer que se llama DateUtils por lo que para usarlo necesitas hacer lo siguiente:

boolean isWeekend = DateUtils.isWeekend(myDate);  

Para evitar este tipo de métodos estáticos Kotlin provee un mecanismo para añadir funcionalidad a clases que no tenemos acceso: Extensiones. La idea es añadir un método llamado isWeekend() a cualquier objeto de tipo Date. Para ello, simplemente hay que declarar la función de una forma concreta:

1. fun Date.isWeekend(): Boolean {  
2.   val calendar = Calendar.getInstance()  
3.   calendar.time = this  
4.   val dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK)  
5.   return dayOfWeek == Calendar.SATURDAY || dayOfWeek == Calendar.SUNDAY  
6. }  

La sintaxis es muy simple. Se tiene que indicar el nombre de la clase la cuál se quiere extender su funcionamiento, punto y el nombre del método. A partir de ese momento, en el cuerpo de la función tenemos acceso a this que es un puntero al objeto dónde se ha llamado el método. Fíjate como en la línea número 3 se usa this como la fecha en la que queremos calcular el día de la semana ya que no se recibe ninguna fecha como argumento de la función.

¿Cómo se usaría? Muy fácil, simplemente llamando al método en cualquier objeto de tipo Date:

val isWeekend = myDate.isWeekend()  

Sencillo ¿no? 😊

Fragment Manager

Te voy a enseñar otro ejemplo, esta vez enfocado a Android.

Para darte un poco de contexto, en Android cuándo se quiere por ejemplo reemplazar un Fragment es necesario crear una transacción a través del objeto FragmentManager. Ejemplo:

val fragmentTransaction = fragmentManager.beginTransaction()  
fragmentTransaction.replace(R.id.container, newFragment)  
fragmentTransaction.commit()  

Es una de las transacciones de Fragments más básicas que existe pero es ideal para ilustrar el ejemplo. Si te fijas la transacción se divide en tres partes bien diferenciadas:

  • Inicialización
  • Cuerpo de la transacción
  • Finalización
// Initialising transaction
val fragmentTransaction = fragmentManager.beginTransaction()

// Transaction body
fragmentTransaction.replace(R.id.container, newFragment)

// Committing transaction
fragmentTransaction.commit()  

¿Cómo sería la extensión entonces? Añadiríamos un nuevo método a FragmentManager que recibe una lambda o función dónde detallaremos las operaciones a realizar en la transacción. Además, está función recibirá como argumento la transación dónde realizar las operaciones:

fun FragmentManager.executeTransaction(operations: (FragmentTransaction) -> Unit) {  
   val transaction = beginTransaction()

   transaction.operations()

   transaction.commit()

}

Puede parecer muy complicado pero ahora cuándo veas cómo se usa verás que realmente es más sencillo de lo que parece:

fragmentManager.executeTransaction { transaction ->  
   transaction.replace(R.id.container, fragment)

}

Sencillo. Simplemente llamar al nuevo método con una lambda detallando las operaciones a realizar, en este caso un replace().

¿Ventajas? De esta manera, aparte de ahorrar boilerplate al tener que escribir beginTransaction()
 y commit() cada vez te asegura qué no te vas a olvidar de commitear la transaccion al final, de esta manera consegues que tu código sea mucho más robusto.

Bonus

Hasta aquí lo básico sobre extensiones pero por si te has quedado con ganas de más voy a enseñarte como mejorar aún más esta función de extensión.

Para empezar, te puedes ahorrar escribir transaction en la cabecera de la lambda usando la palabra reservada it. A modo de resumen, it hace referencia al argumento de una lambda cuándo sólo hay un único argumento.

fragmentManager.executeTransaction {  
   it.replace(R.id.container, fragment)

}

Queda mucho más limpio ¿verdad? Pues aún se puede hacer más. Recuerda cómo se pasa como argumento la transacción dódne ejecutar las operaciones a la función que recibe executeTransaction. Ahora, ¿y sí en vez de pasar la transacción como argumento haces que dicha función sea una extensión de FragmentTransaction? Pues dicho y hecho:

fun FragmentManager.transaction(operations: FragmentTransaction.() -> Unit) {  
   val transaction = beginTransaction()

   transaction.operations()

   transaction.commit()

}

La diferencia simplemente es que ahora dentro de la lambda de operaciones no hay que referirse de ninguna manera a la transación ya que la función se está ejecutando dentro de la transación en sí:

fragmentManager.executeTransaction {  
  replace(R.id.container, fragment)

}

Eso es todo. Esta última parte puede parecer un poco más complicada pero realmente es más sencillo de lo que parece, al final el principio es el mismo: cualquier clase puede ser extendida con métodos o funciones nuevas.

Si tienes cualquier duda o sugerencia no dudes en hacédmela llegar, estaré encantado en intentar resolverla.