Reducers y estados en The Composable Architecture

Albert Gil Escura
7 min readMay 25, 2021

--

En El arte de destruir software, Greg Young no nos habla de que destruir software es un arte. En vez de eso, hace toda una ponencia de que si construimos el software como una unión de piezas pequeñas, independientes, separadas y desacopladas, éstas pueden ser borradas y ser reescritos de nuevo sin preocuparnos de romper nada más que esa misma pieza.

Cuando apareció por primera vez The Composable Architecture y los videos de Point Free, lo primero que me recordó fue a esta genial charla de Greg Young.

Hoy, vamos a jugar con un pequeño formulario. El resultado final, será algo parecido a esto.

Vamos a empezar creando un Switch, en SwiftUI el objeto se llama Toggle. Necesitamos un booleano y una acción que nos permita cambiar el estado de dicho booleano.

En ContentView.swift, añadiremos lo siguiente. En TCA, tenemos un estado, unas acciones y un entorno. Las acciones nos permiten cambiar el estado a través de un reductor.

Y el objeto principal que engloba todo se llama el Store.

WithViewStore nos permite limitar el flujo de refresco de la vista, solamente, a los eventos propios de esa vista. Si usáramos el Store directamente, el rendimiento de la aplicación bajaría drásticamente.

En este episodio, se explica con más detalle el problema.

Pero, lo importante aquí, es el objeto Toggle. El valor de Toggle se obtiene (get) del estado y la acción se envía (send) a través de la acción. Y ya está, tenemos el objeto bindeado.

Además, podemos obtener el valor actualizado en más sitios, por ejemplo, cambiando el color.

Otra cosa interesante, es que el reductor nos permite cambiar el estado, pero el objeto no devuelve nada (.none). Significa que el cambio no genera efectos colaterales (side effects).

Si queremos previsualizar los cambios, actualizaremos tanto el preview como el main.

Ahora vamos a añadir un objeto TextField.

  • Para el componente TextField, vamos a necesitar que el estado guarde un string.
  • Para conocer si el textField coincide con el valor Hello, World! vamos a necesitar guardar un booleano.
  • Necesitamos una acción para actualizar el valor del textfield.
  • Por último, el reducer actualizar tanto el cambio del valor del textfield, como comprobar si el textfield coincide con el valor buscado.

Añadiremos una sección nueva en la vista. El objeto TextField tiene la misma especificación que el objeto Toggle. Necesitamos un getter y un sender. Recuerda, el get corresponde al valor del estado y el send corresponde al case de la acción.

Por último, los valores de los estados los podemos usar en cualquier parte. En este caso, los estamos usando para habilitar/deshabilitar objetos y para modificar el color.

Ahora vamos a añadir un contador. Muy parecido al artículo anterior. Añadiremos un valor que guarde el contador actual, y luego dos acciones que nos permitan aumentar o disminuir el contador.

Por último, actualizaremos la vista con los siguientes elementos.

Aquí no hay bindings. Sencillamente interactuamos directamente con las acciones propias de los botones. Y, seguimos, con los patrones anteriores para interactuar con la vista.

Ahora tenemos, una disyuntiva. Podríamos actualizar las dos acciones y agregar que si el contador es mayor de 9, entonces el valor cheeseEnabled lo actualizaríamos a true. Bien, es factible, pero tendríamos repetido en dos sitios.

¡Side Effects! Podemos agregar una acción nueva, un nuevo case, lo llamaremos case checkPickHasTenItems y lo implementaremos en el reducer.

Y, ahora, lo que queremos es que cuando termine de ejecutar la acción de incrementar o disminuir el contador, se ejecute esta acción.

Ahora ya no devolverá .none, sino que devolverá un efecto con el valor de la acción a ejecutar.

Podríamos pensar que el contador es un elemento un poco complejo. Así que vamos a crear un framework con toda la lógica del contador incluida.

Crea un framework nuevo. Llámalo CounterView. Agrega las dos librerías en el framework nuevo.

Crear componentes es un proceso repetitivo. Así que me creé una plantilla para crear un componente genérico.

Descárgalo y cópialo en ~/Library/Developer/Xcode/Templates/File Templates. Ahora crea un archivo nuevo y selecciona Component.

Selecciona que quieres generar ficheros para SwiftUI.

Ahora se trata de poner toda la lógica del contador dentro del framework. Counter.swift se quedaría así. Recuerda que en un framework hay como public todo lo que quiera ser visto desde el programa principal.

La vista se quedaría así.

Y, aquí, llegamos a una de las cosas más interesantes de esta arquitectura. Podemos probar el componente, de forma totalmente independiente. Si seleccionamos el esquema del framework, deberíamos poder ejecutarlo sin problema y funcionaría, … solo el contador, claro.

Llegado a este punto, queremos que el programa principal funcione. Tenemos que, digamos, mezclarlo.

AppState ya no tendrá los valores del contador sino que recuperará el estado directamente del estado del componente.

Como la aplicación hará uso del valor cheeseEnabled, extenderemos el estado para tener una propiedad custom.

Con las acciones nos pasará algo parecido.

El secreto está en el reducer. Aquí está el core de toda la arquitectura. Tenemos que combinar los dos reducers y luego el componente CounterView tiene que ser capaz de notificar al componente principal. Esto es gracias a la función pullback.

Si quisiéramos acceder a uno de las acciones del contador, iríamos al case .counter y procederíamos a modificar el estado o generar una side effect.

Para finalizar, sería actualizar la vista y ya lo tendríamos. He subido el proyecto para que se pueda ver con calma.

Aquí unos tips rápidos.

  • Todo componente tiene un estado (struct), unas acciones (enum), un entorno (struct) y un reducer (class).
  • El Store es la clase que relaciona el modelo con la vista.
  • Para interactuar con la vista, hay que acceder a la viewStore
  • Si usamos más componentes hay que combinarlos y usar la función pullback
  • Si nos habituamos a escribir un componente por cada framework, estamos limitando su alcance y podemos, en un futuro, borrar ese componente tranquilamente sabiendo que no romperemos más que ese mismo componente de la aplicación entera.

El arte de destruir software

Para próximos artículos, ya entraremos de lleno en los Side Effects y crear un Environment con instancias a los típicos servicios que tiene cualquier app (API, CoreData,…). Y, testing, aunque si ya trabajaste con RxTest y marbles, aquí testear es muy sencillo y ayuda mucho para controlar los side effects, sobretodo si te has dejado alguno.

Más información en la web de PointFree.

Happy coding!

--

--

Albert Gil Escura