Como empezar con Riverpod, StateNotifier, Freezed

Elian Ortega
9 min readApr 14, 2021

--

💡 Como utilizar las 3 herramientas en conjunto para manejar el estado de tu aplicación.

🇺🇸 Click here for the English version

Cada vez escuchamos más de Riverpod como la nueva solución para manejo de estado en Flutter. Pero el gran problema es que mucha gente al tratar de usarlo no sabe ni por donde empezar.

En este artículo me voy a concentrar en como se pueden estructurar un proyecto utilizando Riverpod,StateNotifer y Freezed en conjunto para crear el flujo completo de manejo de estados.

Vamos a construir una aplicación de chistes, esta utilizará Jokes API para obtener chistes de programación y luego los mostraremos en pantalla. Obtendremos algo así:

Podría realizar una explicación detallada de cada una de las herramientas, pero no hay que reinventar la rueda mi amigo Marcos Sevilla tiene unos artículos excelentes explicando estas herramientas.

Ahora que ya saben que son cada una de estas herramientas y su funcionamiento individual vamos a ver como podemos utilizarlas en conjunto para obtener nuestro setup de manejo de estado.

¡ ⚠️ Antes de continuar es importante y que conozcan los conceptos básicos de las herramientas así que lean la documentación!

¡Ahora sí, empecemos!

Para mantener un código ordenado la estructura de carpetas es clave, así que vamos a partir que nuestro código está separado por features. Si has utilizado flutter_bloc has visto que es muy común la creación de una carpeta bloc con sus respectivos estados y eventos y una carpeta de vistas para la parte de UI de nuestro feature, algo así:

Carpeta generada con la extensión de VS Code de bloc v5.6.0

Como pueden ver tenemos un feature llamado jokes en este se encontrará la lógica y vistas de nuestra app de chistes. Esta estructura puede que para muchos sea familiar por lo que ya han utilizado flutter_bloc.

Ahora antes de ver el código veamos la estructura que queremos alcanzar utilizando Riverpod, StateNotifier y Freezed:

Pueden ver que es una estructura muy similar a la propuesta por flutter_bloc, en este caso por como funciona riverpod tenemos los siguientes archivos:

  • jokes_state.dart: Es el archivo donde definimos los posibles estados que puede emitir el StateNotifier. Comúnmente son: initial, loading, data y error.
  • jokes_state.freezed.dart: Este es un archivo que se genera con toda la información de las clases que definimos en jokes_state.dart, no nos debemos de preocupar mucho por su contenido, ya que es código generado.
  • jokes_state_notifier.dart: En este archivo se encuentra la definición del JokesNotifier que es una implementación de un StateNotifier , este será el core de nuestro manejo de estado, ya que en esta clase es donde definimos los métodos que se encargaran de cambiar y emitir los nuevos estados cuando sea necesario.
  • jokes_provider.dart: En este archivo es donde definimos los diferentes tipos de Providers que vamos a utilizar en este feature. En este caso van a ser 2, el primero es un Provider común para el repositorio de donde obtendremos chiste desde el API y el segundo un StateNotifierProvider que es un provider especial para los StateNotifier. O en otras palabras podríamos decir que en este archivo es donde se hace la inyección de dependencias del feature.

¿Pero con este último punto, que ocurre si tenemos 2 features distintos que utilizan el mismo provider?

Muy sencillo, podemos crear una carpeta donde se encuentren un provider global que sea utilizado por varios features al mismo tiempo. Algo así:

Y si ya leyeron la documentación lo saben, pero por si acaso les recuerdo que con riverpod combinar providers es muy sencillo.

Por ejemplo imaginemos el escenario de una tienda en donde definimos un Provider para los métodos de pago ( PaymentMethods) y otro provider para él Checkout , en este caso el provider del Checkout necesita la instancia de PaymentMethods para poder funcionar y gracias al ProviderReference realizar esta relación es muy sencillo:

📖 Si todavía no entienden muy bien esa parte, les recomiendo ir a leer los artículos de las herramientas que les deje al inicio.

Ahora si veamos, el código.

Se podrán reír de este paso, pero se asombrarían de la cantidad de veces que se olvida. Para utilizar riverpod hay que agregar un ProviderScope widget como raíz de nuestra aplicación, este permite la comunicación de los providers.

En lo personal me gusta separar el main.dart del resto de la aplicación y creo un app.dart que contiene el root de la aplicación y cualquier configuración extra que se necesite.

Y así de fácil estamos listos para empezar. Recuerden incluir las dependencias en el pubspec.yaml, para el momento que escribo este artículo son:

Antes de poder programar algún tipo de lógica necesitamos crear los modelos que representen a los objetos de nuestra aplicación. En este caso nos vamos a guiar a partir de la documentación brindada por Jokes API, en donde nos dicen que el formato de respuesta es este:

Solo con ver el formato de la respuesta podemos identificar los 2 modelos que vamos a utilizar. El primero es un modelo para las posibles banderas del chiste, lo llamaremos FlagsModel y otro modelo para todo el chiste que llamaremos JokesModel.

Estos objetos utilizan equatable y json_serializable para generar los metodos fromJson() y toJson().

Una vez creada estas 2 clases es necesario correr los comando de build_runner ya que json_serializable debe de generar los métodos de parsing de las clases.

Corremos los siguientes comandos:

Ya con esto no deberíamos tener errores de sintaxis en nuestros modelos y podemos seguir con el siguiente paso.

En esta ocasión no voy a enfocarme mucho en este paso, ya que no es el objetivo, si quieren ver más ejemplos o documentación de arquitectura limpia y sus componentes pueden chequear mis redes donde tengo otros artículos y videos hablando del tema.

La razón de crear el repositorio con una interfaz además de las buenas prácticas es para que vean como se puede realizar la inyección de dependencias con interfaces cuando creemos los providers.

El jokes_repository.dart contiene la interfaz IJokesRepository y JokesRepository que es la implementación. Estas solo tienen un método Future<JokeModel> getJoke(); que hace un llamado al Jokes API para obtener un chiste, si ocurre un error se lanza una Exception, de lo contrario devolvemos un JokesModel.

En este caso nuestro feature de jokes no es muy complejo, el objetivo es hacer el llamado a una API, y reaccionar a los diferentes estados desde él UI de la aplicación. Entonces, vamos a tener 4 estados: initial, loading, data (cuando ya tengamos el número aleatorio) y error (cuando ocurre un error) para poder darle feedback al usuario de lo que está pasando.

También podemos agregar un extension method para simplificar la manera en la que revisamos si el estado es loading.

Una vez ya definimos la estructura de los estados con los respectivos annotations de Freezed debemos correr un comando para que se cree el archivo jokes_state.freezed.dart con código generado.

En la terminal escribimos:

Con esto ya vamos a generar los archivos de Freezed.

⚠️ El código de jokes_state.freezed.dart es generado así que no te estreses o intimides si no lo entiendes, este no debería de ser modificado.

Como les mencione anteriormente este es el core de nuestro manejo de estados.

En esta clase es donde haremos los llamados al repositorio y asignaremos los estados para que sean notificados a los componentes en él UI.

Pueden observar que lo primero que hacemos es en el método super() de la clase asignamos el primer estado a JokesState.initial(). Después, solo tenemos un método getJoke() que realiza un llamado al repositorio dentro de un try{}catch{} ya que como vimos anteriormente en algunos escenarios vamos a lanzar una excepción. A base de esto asignaremos el nuevo estado ya sea state = JokesState.data(joke: joke); cuando el llamado al API sea exitoso o state = JokesState.error('Error!'); cuando se lanza una excepción y es capturada por el catch().

Hasta este punto ya hemos creado todos los componentes necesarios para que función la lógica de la aplicación. Ahora debemos crear 2 providers, primero los Providers que inyecten las dependencias en este caso la del JokesRepository con su respectiva interfaz y segundo los Providers que expongan los estados en este caso un StateNotifierProvider que exponga el JokesNotifier para que podamos reaccionar a los cambios de estados desde él UI de la aplicación.

Pueden ver que como les mostré previamente en el ejemplo de PaymentMethods/Checkout realizar la combinación de los providers es muy sencillo, en este caso utilizo él ProviderReference (ref) para inyectar la dependencia que tiene el JokesNotifier del repositorio.

Tal vez algunos se pregunten, ¿por qué declare _jokesRepositoryProvider como una variable privada? Esto es simplemente por buenas prácticas, con esto evitas cometer el error de hacer llamados directamente al repositorio desde él UI.

Con esto hemos concluido la implementación de toda la lógica necesaria para esta aplicación. Ahora solo nos queda implementar él UI y reaccionar a los estados.

En este caso solo tenemos una página en él UI para simplificar el ejemplo. Pero antes de mostrarles el código quiero recalcar 2 puntos importantes.

Obviamente recuerden leer la documentación, pero les recuerdo cosas importantes.

Como llamar un método de un provider

Si solo deseas llamar un método, en este caso necesitamos llamar el método getJoke() del StateNotifier:

Como escuchar los cambios de estado de un provider

Si deseas escuchar los cambios de estado emitidos por un StateNotifier hay diferentes maneras, pero la más sencilla es utilizar un ConsumerWidget este nos da acceso a una propiedad llamada watch de tipo ScopedReader con esta podemos escuchar los diferentes cambios de estado del provider que elijamos:

En nuestro caso es todavía más sencillo porque cuando utilizamos freezed para la creación de estados tenemos acceso a un método when(), este nos permite reaccionar a cada uno de los estados de manera más sencilla evitando el problema de muchos if - else en el código.

En nuestra implementación tendríamos algo así:

Este es un ConsumerWidget de nuestra página que se encarga de devolver el widget indicado para cada uno de los diferentes estados.

Y con esto ya podemos crear nuestra página completa:

Y después de todo este código obtenemos este resultado:

No es UI más bonito del mundo, pero el enfoque no era ese, sino aprender a utilizar riverpod, freezed y statenotifier en conjunto como un manejador de estado.

Este es un ejemplo bastante básico si quieres ver un ejemplo con un setup completo de arquitectura limpia te dejo estos links:

Aplicación con API de chistes hecha con riverpod y cumpliendo principios de clean_architecture

Aplicación con API de Covid 19 y un setup mas simple

Aplicación generador de QR con todo el testing de flutter_bloc y riverpod + clean architecture

Para el futuro planeo compartir artículo de como hacer el testing completo y otras cositas.

Código en Github

Si quieren ver el código les dejo el link al repositorio aquí.

Si tiene alguna duda o comentario no duden en contactarme.

Para terminar…

Gracias a Marcos Sevilla por este outro 😂, el también comparte contenido muy bueno les dejo su Twitter para que estén al tanto.

Si aprendiste algo nuevo y te fue de utilidad, podés compartir este artículo para ayudar a otro/a desarrollador(a) a seguir mejorando su productividad y calidad al escribir aplicaciones con Flutter.

También hay una versión de este mismo artículo en inglés publicado en dev.to. De nada. 🇺🇸

Además, si te gustó este contenido, podés encontrar aún más y seguir en contacto conmigo en mis redes sociales:

  • dev.to — donde publico versiones en inglés de mis artículos.
  • GitHub — donde están mis repositorios de código por si te gustan los ejemplos.
  • GitHub — donde repositorios del contenido que compartimos Marcos y yo (clean architecture, test, etc)
  • LinkedIn — donde conecto profesionalmente.
  • Medium — donde estás leyendo este artículo.
  • Twitter — donde expreso mis ideas cortas y comparto mi contenido.
  • Twitch — donde hago directos informales de los que saco clips con información puntual.
  • YouTube — donde publico los clips que salen de mis directos.

Originally published at http://github.com.

--

--

Elian Ortega
Elian Ortega

Written by Elian Ortega

I focus on writing high-quality, scalable, and testable applications. I like to write articles and make videos about tech.

Responses (1)