Categorías
Uncategorized

Arquitectura Redux para SwiftUI

En el desarrollo de aplicaciones modernas, manejar el estado de manera eficiente y escalable es crucial. Una de las arquitecturas más populares para la gestión del estado es Redux, que ofrece una manera clara y predecible de manejar el estado de la aplicación. Este artículo explora cómo implementar una arquitectura Redux en una aplicación SwiftUI utilizando ReSwift y ReSwiftThunk, junto con el patrón de UseCases para encapsular la lógica de negocio. Pero si eres como yo, y te gusta echar manos al código de manera directa, puedes revisar el repositorio con la implementación aquí (https://github.com/Transmigrado/ArquitectureSwiftUI), si aún no conoces redux y te gustaría explorarlo como alternativa a tus desarrollos en Swift usando SwiftUI, este articulo te ayudara a partir.

La motivación para implementar una arquitectura Redux en uno de los proyectos en los que estoy trabajando, nace de la necesidad de tener arquitecturas mas compatibles con las formas de trabajos mas modernas como es Jetpack Compose y en el caso de iOS SwiftUI, estás dos tecnologías declarativas para poder crear interfaces en entornos móviles, resultan muy compatibles con arquitecturas reactivas como Redux, permitiendo ciclos de vida bastantes amigables para mantener.

En el proyecto se uso Firebase por temas de comodidad, lo que se quería explorar era como implementar una arquitectura que sea manejado por acciones para modificar los estados de la aplicación. Tu siéntete libre de usar el proyecto, agregando tu propio consumo de datos, ya que la arquitectura esta totalmente aislada de la lógica de negocio, la cual se encapsulo en casos de uso (UseCases)

 

¿Qué es Redux?

Redux es una arquitectura de manejo de estado predecible diseñada inicialmente para aplicaciones JavaScript, especialmente aquellas construidas con React. La idea principal detrás de Redux es tener un único store (almacenamiento) que contiene el estado de la aplicación y que sólo puede ser modificado a través de acciones. Un reducer, que es una función pura, recibe la acción y el estado actual, y devuelve un nuevo estado.

 

¿Qué es ReSwift?

ReSwift  es una implementación de Redux en Swift para aplicaciones iOS. Redux es un patrón de arquitectura predecible para el manejo del estado de la aplicación, inicialmente popularizado por aplicaciones JavaScript como las creadas con React. ReSwift lleva este patrón al ecosistema de Swift, ofreciendo una manera clara y estructurada de manejar el estado de tu aplicación iOS.

 

¿Qué es ReSwiftThunk?

ReSwiftThunk  es una extensión de  ReSwift  que permite manejar la lógica asincrónica y las acciones secundarias. Thunks son funciones que pueden despachar otras acciones o ejecutar lógica adicional antes de despachar una acción. En ReSwift, los Thunks ayudan a mantener el estado de la aplicación limpio y predecible mientras permiten realizar operaciones asincrónicas como llamadas a APIs, base de datos (CoreData, RealM, etc) o cualquier efecto secundario que requiera la modificación del estado global de la aplicación.

puedes encontrar estás dos librerías en sus respectivos repositorios:

Repositorio de ReSwift: (https://github.com/ReSwift/ReSwift)

Repositorio de ReSwift-Thunk: (https://github.com/ReSwift/ReSwift-Thunk)

 

El flujo de datos en esta arquitectura se vera algo así, por lo general la UI disparara una acción, esta acción sera recibida por el reducer, que modificara el state y a su vez, este estado sera escuchado por nuestro componente UI, además para poder lanzar acciones secundarias, como llamar a un api o a la base de datos, utilizaremos un thunk, el thunk sera una especie de puente entre el flujo redux y nuestra lógica de negocios, que estará encapsulada en un UseCase

 

¿Qué es un Reducer?

Un reducer es una función pura que toma el estado actual de la aplicación y una acción como argumentos, y devuelve un nuevo estado. La función reducer no debe tener efectos secundarios, es decir, no debemos usar esta función para llamar apis, bases de datos locales o modificar la UI, solo modificamos las propiedades en base a una acción, como se muestra a continuación

De esta manera podremos crear los test unitarios necesarios para probar un reducer, simplemente debemos declarar un estado y la acción que queremos probar, luego que el reducer es invocado con dicha acción, el nuevo estado debe cumplir nuestras condiciones.

¿Qué es un Thunk?

un thunk es una función que se utiliza para manejar acciones asíncronas o acciones que no resultan en cambios inmediatos de estado. En otras palabras, nos permite que una acción despachada no sea solo un objeto de acción simple, sino que puede ser una función que contenga lógica adicional, como llamadas a API, consultas a una base de datos local o cualquier otra operación asíncrona, antes de despachar una acción regular.

Un thunk sera función que no devuelve directamente un objeto de acción, sino que devuelve una función que recibe el dispatch como argumento. Esa función puede ejecutar lógica asíncrona y luego despachar una o más acciones basadas en el resultado de dicha lógica.

Como describimos anteriormente, usaremos un Thunk para ejecutar un efecto asíncrono, en este caso llamaremos a fetchAsyncData de nuestro useCase para traer los posts y luego despachar una acción para que el estado de nuestra aplicación cambie.

Aquí podemos ver como funciona un thunk comunicando con el flujo que describimos al principio, detallaremos cada paso.

PostThunks

La clase PostThunks actúa como un contenedor para la lógica asíncrona que recupera los posts desde un servicio o caso de uso.

  • Método fetchThunk():
    Este método devuelve un Thunk, que es una función que permite ejecutar operaciones asíncronas antes de despachar acciones en el flujo de Redux.

    • Thunk fetchThunk:
      Este thunk recibe dispatch (que permite despachar acciones) y getState (que permite acceder al estado actual) como argumentos. Aquí es donde se gestiona el flujo de las acciones así

      • Dispatch(FetchAction()): Antes de ejecutar la tarea asíncrona, se despacha una acción llamada FetchAction, lo que probablemente indica en la interfaz de usuario que la aplicación está cargando (como mostrar un indicador de carga).
      • Task {}: Se usa un bloque Task para ejecutar código asíncrono dentro del contexto del thunk. Aquí se llama al método fetchAsyncData() desde el useCase, que es una llamada asíncrona que recupera los datos de los posts.
      • Dispatch(PostsAction(list: posts)): Si la llamada a la API tiene éxito, los posts recuperados se despachan a través de la acción PostsAction, lo que actualizará el estado de la aplicación con la lista de posts.

 

PostsState:

Esta estructura define cómo se representa el estado relacionado con los posts en la aplicación.

  • list: Almacena una lista de posts que se mostrará en la aplicación.
  • isLoading: Indica si la aplicación está actualmente en el proceso de cargar los datos.
  1. postsReducer :

Este reducer tiene  la función de tomar una acción y el estado actual de la aplicación y devuelve un nuevo estado, actualizado según la acción.

  • FetchAction: Cuando se despacha esta acción, el reductor cambia el valor de isLoading a true, lo que indica que la aplicación está en proceso de obtener datos.
  • PostsAction: Cuando los posts son recuperados y despachados, esta acción actualiza la lista de posts en el estado (state.posts.list) y pone isLoading en false, indicando que la carga ha terminado.
  1. Flujo general del thunk:
  • Al llamar a fetchThunk(), el thunk despacha la acción FetchAction para indicar que la carga de los posts ha comenzado.
  • A continuación, realiza una llamada asíncrona para recuperar los posts a través del useCase.
  • Si la llamada es exitosa, despacha la acción PostsAction con la lista de posts obtenidos, lo que actualiza el estado.
  • Si la llamada falla, simplemente imprime el error sin afectar el estado (aunque podría mejorarse con una acción de error).

 

En conclusión, la combinación de Redux con ReSwift y ReSwiftThunk ofrece una arquitectura robusta y predecible para manejar el estado en aplicaciones SwiftUI. Al encapsular la lógica de negocio en UseCases y manejar las acciones asíncronas mediante Thunks, se logra un flujo claro y eficiente para gestionar el ciclo de vida de los datos. Este enfoque no solo permite un código más organizado y testeable, sino que también facilita la implementación de arquitecturas escalables y mantenibles en proyectos móviles modernos. Invitar a los desarrolladores a explorar esta arquitectura es una excelente oportunidad para mejorar sus habilidades en el manejo de estado y lógica asíncrona en aplicaciones SwiftUI.

 

Por: Jorge Acosta Tech Lead

es_CL