Redux: Gestor de estado para Flutter
- Andrés Cruz
En el desarrollo de aplicaciones con Flutter tienes que juntar la continua variación o cambio de los datos (mutabilidad), con la incertidumbre de cuándo se producen (asincronismo) los mismos.
Para aplicaciones pequeñas esta combinación no resulta tan problemático ya que existen múltiples formar en la cual puedes llevar este proceso a cabo; de una manera más manual; por ejemplo si tenemos las siguientes pantallas:
Como puedes ver, es una app muy pequeña, con pocas pantallas relacionadas, en este caso, simplemente dos, una página de listado y otra para modificar estos registros, lógicamente si en el listado de un ítem en particular aparece por ejemplo un título, y quieres modificar el mismo, vas a la siguiente pantalla para editar el mismo y luego de que vuelvas a la pantalla anterior, ese cambio debería verse en el listado; para esto existen múltiples maneras de hacerlo, puedes emplear métodos del propio ciclo de vida de una app en Flutter, puedes disparar la función de promesa del navigator para refrescar los cambios y muchas otras cosas.
Aquí, el factor clave que quiero que entiendas del ejemplo anterior, es que hablamos de incertidumbre, de estos cambios o mutabilidad que pueden ocurrir en cualquier momento.
El problema viene cuando tenemos muchas pantallas relacionadas, por ejemplo en una aplicación de tienda en línea, que aunque la que vamos a llevar a cabo en el curso, por cuestiones de ser prácticos no va tener muchas pantallas, pero que en general este tipo de aplicaciones cuentan con un buen número de pantallas relacionadas; y a que me refiero con pantallas relacionadas:
Por ejemplo la pantalla anterior, que tenemos una app un poco más grande, y algunas pantallas relacionadas, para este ejemplo, tenemos una pantalla de listado de productos, de nuestra pequeña tienda, y múltiples pantallas y opciones para modificar esta data, para mutar la misma, con esto, tenemos que implementar un estado para indicar estos cambios a las pantallas anteriores, ya sea que mediante estas opciones de ejemplo, agreguemos el producto a favorito, le cambiemos el título, agreguemos un comentario y por ende en la pantalla de detalle tenemos que indicar un comentario más o al menos actualizar el total de comentarios, agregamos o removemos el producto del carrito... en pocas palabras un montón de mutabilidad en la data, un montón de cambios que a la final tenemos que reflejar en nuestra aplicación en múltiples pantallas; aquí pudiéramos seguir con la idea inicial, de preguntar el estado del producto cada vez que regresemos a la pantalla anterior, preguntar si, fue agregado a favoritos, lo eliminamos, al carrito y un largo etc, pero como puedes suponer ya esto es un pequeño infierno y si lo hacemos de la manera que te indicaba anteriormente estamos ensuciando la aplicación, nuestras páginas con mucha lógica de negocios que solamente nos sirve para mantener el estado de la aplicación.
Gestión de estado
Para estos casos existen múltiples formas en la cual nosotros podemos mantener ese estado de la aplicación en Flutter.
Ya en el curso, a esta altura vimos uno, el patrón llamado Bloc, que puedes encontrar múltiples implementaciones, pero también tenemos Cubic, providers y por supuesto Redux y muchos más.
Todos estos esquemas para la gestión de estados de nuestra aplicación, resuelven el mismo problema de maneras distintas, como todo en la vida que tenemos variantes; pero aquí lo importante es notar que existen maneras que nosotros podemos colocar a alcance global de la aplicación o en algunas pantallas relacionadas para que cada vez que ocurra un cambio en la data, la data mute a lo largo de toda la app y se actualicen las pantallas; de tal manera que tengamos actualizaciones automáticas en la app y con esto en la práctica, estamos llevando la gestión del estado de la app.
Los principios fundamentales de Redux
Fuente única de datos, store: En Redux hay un único objeto que almacena el estado de toda la aplicación llamada store; en la práctica tenemos un solo archivo en donde manejamos toda la lógica de negocios, que en caso de nuestra app, sería el acceso a la base de datos local, conexión a alguna Api, gestionar los datos del usuario guardados en la preferencias de usuario y un largo etc; de tal manera que con esta clase u objeto global, podemos gestionar distintos proveedores de datos.
Inmutabilidad, el estado es read-only. Ninguna interacción la puede cambiar directamente. Lo único que puedes hacer para conseguir un cambio en la misma, es emitir una acción que expresa su intención de cambiarlo, y esto obviamente es un clic del usuario, un desplazamiento, la navegación a otra pantalla, cualquier cosa o interacción que nosotros registramos por parte del usuario
Funciones puras: Usa netamente funciones para definir cómo cambia el estado en base a una acción. En Redux estas funciones se conocen como reducers y al ser puras, su comportamiento es predecible; por lo tanto, tenemos funciones que reciben únicamente una acción y el estado actual:
(state, action) => newState
De la aplicación y que simplemente devuelve un nuevo estado (un estado modificado) (ya que no podemos modificar el mismo, porque es inmutable) y es específico a lo cual estamos trabajando; por ejemplo si en el estado tenemos información de usuarios y productos, y el reducers que estamos invocando, se encarga de agregar un producto, entonces el nuevo estado solamente va a devolver lo que compete con los productos y nada del usuario o cualquier otra cosa que tengas en ese estado, y de aquí su nombre de reducers o reductores.
Conceptos claves
Como te comentaba anteriormente, tenemos dos elementos principales, el Store, que es único y global a nuestra aplicación y es el lugar en donde se almacena TODA la data de nuestra aplicación.
Y el State o estado que guarda la información de nuestra aplicación y no se modifica directamente.
El reducer es una función que nos permite crear un nuevo estado en base al viejo estado/state y la acción.
Ciclo básico
- El componente recibe un evento (click, por ejemplo) y emite una acción.
- Esta acción, se pasa a la store, que es donde se guarda el estado único.
- La store comunica la acción junto con el estado actual a los reducers.
- Los reducers, devuelven un nuevo estado, probablemente modificado en base a la acción.
- Los componentes/widgets reciben el nuevo estado de la store.
- La interfaz se actualiza automáticamente
Obviamente a nivel de código todo esto tiene una estructura en particular, pero eso lo veremos en la práctica.
Diagrama básico del Redux:
Diagrama con el Middleware Redux:
Esta variación es la que nosotros vamos a emplear en el curso, y es que necesitamos un Middleware que nos permita realizar ciertos llamados, a nuestra Api; nuestra Api puede ser una base de datos en SQlite, Sembast, nuestro SharePreference y un largo etc; finalmente con la respuesta de la Api, vamos a enviar la respuesta al Reducer que interactúa con nuestro Store, nuestros datos y finalmente devolverá una respuesta a nuestra vista para que refresque los widgets.
Otro punto muy importante es que Redux es una librería que nació para JavaScript y cuya estructura o patrón fue migrado para Flutter; así que si quieres buscar más información, cosa que te recomiendo seguramente vas a encontrar mucha información del mismo aplicado a JavaScript
Enlaces de interés
- http://blog.enriqueoriol.com/2018/08/que-es-redux.html
- https://www.youtube.com/watch?v=sgFQjRL5niY
Desarrollo con Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter