Propiedades Observables En Kotlin

En este tutorial aprenderás sobre el uso de propiedades observables en Kotlin a través de los métodos de fabricación estándar del objeto Delegates. Verás cómo las funciones delegate() y vetoable() crean delegados para tus propiedades que permiten observar los cambios de sus valores.

Al igual que las propiedades lazy, las propiedades observables son delegados estándar que nos provee el lenguaje para ahorrarnos la escritura de clases que observen modificaciones de propiedades.

La Función delegate()

La función delegate() hace parte de la declaración de objeto Delegates de la librería estándar de Kotlin.

Esta crea un delegado para la propiedad que desees, cuya función es registrar una escucha que te avisa cuando hubo un cambio en tu propiedad.

Usa la siguiente sintaxis para ejecutar una acción cuando el valor cambie:

inline fun <T> observable(
    initialValue: T,
    crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit
): ReadWriteProperty<Any?, T>

El parámetro initialValue es el valor inicial que se le asigna a la propiedad y onChange es la callback que se ejecuta luego de realizado un cambio en la propiedad.

Teniendo esto en cuenta, invoquémosla en un ejemplo.

Ejemplo:

Actualizar los valores de subtotal e iva cuando el total es modificado.

import kotlin.properties.Delegates

var subtotal = 0.0
var iva = 0.0
var total by Delegates.observable(0.0) { _, _, new ->
    computeTotals(new)
}

private fun computeTotals(total: Double) {
    iva = total * 0.19
    subtotal = total - iva
}

Debido a que la variable total es el origen de los cambios, le creamos un delegado con observable(), para que ejecute la lógica de la función computeTotals() cuando se escuchen cambios.

Ahora probemos asignar un valor a subtotal e imprimir las tres variables:

fun main() {
    // Cambiar valor de propiedad observable
    total = 150.0

    println("Subtotal: $subtotal")
    println("Iva: $iva")
    println("Total: $total")
}

Salida:

Subtotal: 150.0
Iva: 28.5
Total: 121.5

La Función vetoable()

Usa la función Delegates.vetoable() para crear un delegado con la capacidad de vetar la asignación de un nuevo valor, antes de que se produzca el cambio.

Dicha intervención antes de la modificación es posible, gracias al cuerpo Boolean del parámetro onChange. Si es true, la asignación se cumple, de lo contrario limitas el valor de la propiedad al anterior.

inline fun <T> vetoable(
    initialValue: T,
    crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Boolean
): ReadWriteProperty<Any?, T>

Ejemplo:

Permitir únicamente la asignación de números que son pares a una propiedad entera. Adicionalmente, el nuevo valor a asignar tiene que ser mayor que el antiguo.

fun main() {
    var evenNumber by Delegates.vetoable(0) { _, old, new -> isAsignable(new, old) }

    evenNumber = 3
    println(evenNumber)

    evenNumber = 10
    println(evenNumber)
    
    evenNumber = 8
    println(evenNumber)
}

private fun isAsignable(new: Int, old: Int) = new % 2 == 0 && new > old

Salida:

0
10
10

La solución usa a vetoable() para interceptar asignaciones de la propiedad local evenNumber.

El cuerpo de la función lambda usada es el resultado de isAsignable(). Una operación booleana que encapsula las comprobaciones de: el número por asignar es par y mayor que el valor antiguo.

Cuando asignamos 3 a evenNumber, el valor no es modificado ya que este no es par. Luego asignamos 10 y esta vez sí hay éxito en el cambio. Por último asignamos 8, que aunque es par, no es mayor a 10, por lo tanto no se permite el cambio.

¿Ha sido útil esta publicación?