Principios básicos de Compose (Parte 4)

Yago Rey
3 min readMay 27, 2023

--

Estado

Los ejemplos que hemos visto hasta ahora mostraban una interfaz de usuario que se puede representar como una simple transformación o proyección de datos.

La realidad es que la mayoría de las IU almacenan varias piezas de estado que no tienen ningún sentido como parte del modelo de datos general, sino que son específicas de la propia IU (es decir, “estado de vista”).

Por ejemplo, no sería conveniente que un estado (como la selección de texto, la posición de desplazamiento, el enfoque, la visibilidad del diálogo, etc..) tuvieran que ser parte del modelo de datos específico del dominio.

Este estado es debería ser una preocupación de la interfaz de usuario, y nada más.

Compose necesita tener un modelo de estado que maneje este caso de uso de “estado local”. Este modelo podría entenderse mejor si tratamos de construirlo a partir de los conceptos que hemos visto hasta ahora con Memoización posicional.

Para analizar el estado, consideremos el siguiente ejemplo de interfaz de usuario. La interfaz tendrá un contador con un botón de “recuento”, un botón de “incremento” y un botón de “restablecer”. Para empezar, podremos asumir que tendremos un único estado almacenado en una variable a la que llamaremos count:

var count = 0

fun Composer.App(recompose: () -> Unit) {
emit({ Text() }, { memo(count) { it.text = "$count" } })
emit({ Button() }, { it.text = "Increment"; it.onClick = { count++; recompose(); } })
emit({ Button() }, { it.text = "Reset"; it.onClick = { count = 0; recompose(); } })
}

fun main() {
var recompose: () -> Unit = {}
recompose = {
renderNodeToScreen(compose { App(recompose) })
}
recompose()
}

Dado que estamos usando el estado global, si este componente se usa en varios lugares, el estado se compartirá entre todos los usos.

Aunque esto puede ser útil en algunas situaciones, por lo general no es lo que queremos. Queremos poder crear una “instancia” de count que pueda usarse localmente para la “instancia” de la aplicación en todas las composiciones (pongo “instancia” entre comillas aquí, porque en realidad no hay una “instancia” de la aplicación, es solo una función).

¿Cómo podemos hacer esto en Compose?

Lo más básico que debemos intentar es mover el contador a la aplicación como una variable local:

// NOTE: This example does NOT work
fun Composer.App(recompose: () -> Unit) {
var count = 0

emit({ Text() }, { memo(count) { it.text = "$count" } })
emit({ Button() }, { it.text = "Increment"; it.onClick = { count++; recompose(); } })
emit({ Button() }, { it.text = "Reset"; it.onClick = { count = 0; recompose(); } })
}

Esto no funcionará porque la variable count se reinicializará a cero cada vez que se invoque la función.

Ten en cuenta que esto es notablemente similar a cómo se recreaban los nodos cada vez que se llamaba a la función, donde utilizamos la memorización posicional para arreglar eso. ¡Resulta que aquí podemos hacer exactamente lo mismo para el estado local!

class State<T>(var value: T)

fun Composer.App(recompose: () -> Unit) {
val count = memo { State(0) }

emit({ Text() }, { memo(count.value) { it.text = "${count.value}" } })
emit({ Button() }, { it.text = "Increment"; it.onClick = { count.value++; recompose(); } })
emit({ Button() }, { it.text = "Reset"; it.onClick = { count.value = 0; recompose(); } })
}

Ahora que estamos usando memo, la instancia de State será la misma para cada llamada subsiguiente de la función (pero única para su posición en el árbol de la interfaz de usuario). Luego podemos mutarlo y desencadenar una recomposición de la jerarquía para que la pantalla refleje el nuevo valor de la instancia State.

Vale, ya casi casi lo tenemos.

Vamos a ver un último detalle en el siguiente (y último) post que nos permitirá encajar todas las piezas del puzzle.

(

Has leído los posts anteriores? En caso de que sí no es necesario que leas esta aclaración. En caso de que no quiero comentarte que:

  • Este post no deja de ser una traducción casi literal de este otro.
  • Este es un parte de una serie de posts. Si te interesa echar un vistazo al resto te los dejo por aquí:
  • Parte 1
  • Parte 2
  • Parte 3

)

Si quieres escribirme puedes contactarme vía Linkedin a través de este link.

Un saludo!

--

--