Skip to content

Instantly share code, notes, and snippets.

@marcossevilla
Created January 25, 2021 04:03
Show Gist options
  • Save marcossevilla/c9032f844fbc6792cfc2a0cbbf8edcf5 to your computer and use it in GitHub Desktop.
Save marcossevilla/c9032f844fbc6792cfc2a0cbbf8edcf5 to your computer and use it in GitHub Desktop.

StateNotifier, una mejora a ChangeNotifier

StateNotifier, an improved ChangeNotifier

Ya sea que estés empezando o lleves unos años usando Flutter, hay un tema que siempre sale a relucir y tiene mil perspectivas girando a su alrededor: manejo de estado, o state management si sos bilingüe.

El manejo de estado es esencial en Flutter. Muchos lo confunden con arquitectura o creen que una arquitectura depende de un manejador de estado en específico. Pero la verdad es que el manejo de estado es una pequeña parte de la gran arquitectura que puede tener un proyecto.

En ocasiones anteriores ya he hablado bastante sobre este tema, así que mejor les dejo referencias a estas pláticas que he dado:

  • Flutter's Six Paths of State (sí, es referencia a Naruto): Te va a servir para contemplar distintas alternativas para manejar estado en Flutter y elegir la que te haga la vida más fácil.
  • Platicando sobre State Management + Arquitectura: Te va a servir para ver qué papel juega el manejo de estado de tu app en la arquitectura de la misma y sirve como crítica para Six Paths of State.

https://media.giphy.com/media/KcVny4iEXBr7NEsSv7/giphy.gif

Antes de todo, Provider

Probablemente ya estés familiarizado con el paquete de Provider, que no es exactamente un manejador de estado, sino una forma más amigable de usar InheritedWidget. Si no sabes nada sobre InheritedWidget, aquí tengo un video para vos.

En pocas palabras, Provider nos permite usar InheritedWidgets para trabajar con clases o más atómicamente, valores, para manejar nuestro estado. Eso convierte a Provider en una herramienta para comunicación entre widgets, no para manejo de estado como tal.

Si en este preciso momento estoy destruyendo el concepto que tenías de Provider, estoy logrando mi cometido. El plan no es confundirte, es aclarar para que comprendas mejor el por qué de StateNotifier, que es el tema de este artículo. Ya vamos a eso, calma.

Provider en sí puede disponerte de información, a como lo hace un InheritedWidget. Pero los encargados de manejo de estado son las clases que contienen esos datos que dispone Provider. ¿Capisci?

BLoC → Patrón BLoC Bloc → Biblioteca para manejar estados implementando BLoC flutter_bloc → paquete que contiene los widgets para integrar Bloc con Flutter

A esas clases yo les digo BLoCs o componentes de lógica de negocios. Ellos se encargan de manejar esta información interactuando con fuentes de datos externas (un API, por ejemplo) por medio de clases de acceso de datos (servicios o repositorios). Y básicamente ahí se sigue el patrón BLoC, también tengo un video hablando sobre él en este enlace.

Estas clases usadas con Provider usualmente son ChangeNotifier, ValueNotifier o alguna clase personalizada que extienda de estas. Son clases que notifican -como dicen sus nombres- a otras (widgets incluidos) que consumen sus propiedades (por medio de Providers). Eso es lo más típico y es de las formas más simples e iniciales de manejar estado con Flutter.

Aclaro, hay otros paquetes que ocupan Provider para comunicación de sus componentes de lógica con sus interfaces gráficas.

Algunos de ellos son [bloc](https://pub.dev/packages/bloc) y [mobx](https://pub.dev/packages/mobx).

El problema

El problema no es ChangeNotifier, el problema es que depende de Flutter.

ChangeNotifier es una buena alternativa para crear clases que sirvan de BLoC. Pero como pueden saber de mí -si leen otros de mis artículos- disfruto mucho de modularizar mi código y al momento de extraer mi lógica en otros paquetes y hacerlos lo más desacoplados posibles, no me es suficiente. Esto más que nada por su dependencia en Flutter.

Me gusta crear mis paquetes por aparte con la menor cantidad de dependencias posibles. Si puedo evitar el SDK de Flutter mejor.

Les propongo este caso...

Estoy creando la lógica de mi app en Flutter que puedo reutilizar con otras interfaces, así que la hago un paquete. Esa misma lógica me es útil para otra app que me permite llamar código Dart directamente, recordemos que Dart puede ser compilado a binario y eso lo abre a más casos de uso que sólo apps con Flutter. Ya tengo la lógica, pero uso ChangeNotifier, que me hace depender innecesariamente de Flutter.

https://i.pinimg.com/originals/b9/06/d9/b906d9acd5466ee8de5df9ec54e99402.jpg

Hola StateNotifier

StateNotifier es otro tipo de clase que nos sirve de componente de lógica y es una "reimplementación" de ValueNotifier, con la diferencia que no depende de Flutter.

Este paquete con la funcionalidad de ValueNotifier abstraída, nos da el poder de reutilizar nuestra lógica sin depender de Flutter, a como queríamos. Además de que tiene una mejor integración con Provider, porque ambos fueron hechos por el mismo autor.

Luego de haber aprendido bastante sobre flutter_bloc y su estructura, comprendí cómo el manejo de estados como clases era mucho más conveniente y ayudaba a modularizar tu widgets en base al estado actual en el Bloc.

StateNotifier tiene muchas cosas similares a Bloc, entre ellas:

  • Estado inicial en el constructor base.
  • Cambios al estado por medio de métodos (más similar a Cubit que a Bloc).

Una diferencia es que StateNotifier asigna el estado a la variable protegida state (equivalente a value en ValueNotifier) y Bloc utiliza la palabra yield para emitir un estado por lo que ocupa Streams para flujo de datos.

Cómo se usa StateNotifier

  1. Instalamos las dependencias.

    # * si usas Provider
    provider: ^4.3.3
    state_notifier: ^0.6.0
    flutter_state_notifier: ^0.6.1
    
    # * si usas Riverpod, ya integra state_notifier
    flutter_riverpod: ^0.12.2
    
    # usa las dependencias más recientes o 
    # las que sean compatibles con tu proyecto
  2. Creamos los estados.

    abstract class UserState { }
    
    class UserNotLogged extends UserState { }
    
    class UserLogged extends UserState {
    	UserLogged({@required this.user});
    	final User user;
    }
  3. Creamos nuestro StateNotifier.

    class UserStateNotifier extends StateNotifier<UserState> {
    	// usamos super para crear nuestro estado inicial, 
    	// igual que en Bloc
      UserStateNotifier() : super(UserNotLogged());
    
    	// cambiamos el estado asignando uno nuevo
      void logIn(User user) => state = UserLogged(user: user);
    }
  4. Consumimos nuestro BLoC (StateNotifier) en el UI.

    /// * si usamos Provider
    
    void main() => runApp(
    	StateNotifierProvider<UserStateNotifier, UserState>(
    		create: (_) => UserStateNotifier(),
    			child: MyApp(),
    		),
    );
    
    ...
    
    class MyWidget extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
    
    		final userState = context.watch<UserStateNotifier>();
    
        return Scaffold(
          body: Center(
            child: Text('${userState.runtimeType}'),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              context.read<UserStateNotifier>().logIn(User());
            },
          ),
        );
      }
    }
    /// * si usamos Riverpod
    
    void main() => runApp(ProviderScope(child: MyApp()));
    
    ...
    
    final userStateNotifierProvider = Provider<UserStateNotifier>(
    	(_) => UserStateNotifier();
    );
    
    ...
    
    class MyWidget extends ConsumerWidget {
      @override
      Widget build(BuildContext context, ScopedReader watch) {
    
    		final userState = watch(userStateNotifierProvider.state);
    
        return Scaffold(
          body: Center(
            child: Text('${userState.runtimeType}'),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              context.read(userStateNotifierProvider).logIn(User());
            },
          ),
        );
      }
    }

Y eso es un ejemplo sencillo de cómo empezar a usar StateNotifier en lugar de ChangeNotifier o ValueNotifier.

Pueden observar que otro paquete de flutter_state_notifier es usado, al igual que otros paquetes como bloc y flutter_bloc, se incluye un paquete con el prefijo flutter ya que este es el que contiene widgets que son útiles para integrar el otro paquete con tu interfaz gráfica. En este caso nos funciona para usar la clase StateNotifierProvider.

Creando mejores estados

En el paso 2 de la parte anterior pueden observar que creamos un estado abstracto que puede ser usado por cualquier clase por medio de herencia. Esto es una práctica que yo primeramente vi en Bloc, donde se definen eventos que alteran el estado actual de un BLoC. Es muy buena porque nosotros definimos en el BLoC que vamos a mandar algún estado que herede de un estado abstracto, a como lo vimos en el ejemplo.

Con StateNotifier esto queda excelente al pasarle un tipo de dato específico (nuestro estado abstracto) y asignarlo según necesitemos.

El problema de esto es que se nos vuelve un poco tardado y escribimos código repetitivo cuando tenemos varios estados posibles. Por esta razón, vamos a incluir otro paquete llamado Freezed, que funciona de una manera increíble con StateNotifier.

Podemos simplificar el código con un union de Freezed, nos quedaría de esta manera...

https://gist.github.com/c2eb39344f4c132a75ffa60e68aa6b4a

Si te gustaría saber más sobre Freezed para saber cómo autogenerar el código anterior y qué significa, te sugiero leer mi artículo sobre este paquete por acá.

¿Adiós ChangeNotifier/ValueNotifier?

No realmente.

Remi Rousselet, creador de Provider y StateNotifier, ha dado mantenimiento y creado pull requests al repositorio oficial de Flutter para mejorar ChangeNotifier.

https://twitter.com/FlutterMerge/status/1294428072717045761

https://twitter.com/FlutterMerge/status/1298692196951044099

Así que no significa que StateNotifier sea un sucesor. Como siempre:

Es una opción más que pueden escoger.

Yo personalmente uso StateNotifier con Riverpod, la reescritura de Provider, y me gusta bastante la combinación ya que Riverpod lo incluye por defecto. Manejar estados muy parecido a como lo hace flutter_bloc me parece muy conveniente y hace código más predecible (lo cual es muy bueno). Todo eso sumado a que la sintaxis de asignación de estado es más simple y facilita la emisión del mismo.

https://media.giphy.com/media/BWD3CtcudWL28/giphy.gif

Lo de siempre...

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, GitHub, LinkedIn, Medium, Twitter, YouTube.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment