flutter-estado-prop-drilling

Prop drilling y estado compartido en Flutter

  • 4 min

El estado de la aplicación es la información que describe cómo está la app.

Imagina que estás construyendo una aplicación de comercio electrónico. Tienes un “Carrito de la Compra”.

Ese carrito (una lista de productos) necesita ser visible en:

  1. La pantalla de Catálogo (para mostrar un contador “3 items”).
  2. La pantalla de Detalle de Producto (para añadir más).
  3. La pantalla de Checkout (para pagar).

setState solo reconstruye el State al que pertenece. Podemos elevar _carrito a un antepasado común y pasarlo hacia abajo, pero esa solución se vuelve incómoda cuando hay muchos niveles.

Estado local y estado compartido

Para solucionar esto, primero debemos distinguir dos tipos de datos:

Es un estado que solo le importa a un widget específico.

  • Ejemplos: La pestaña seleccionada en un menú, si una animación está corriendo, el texto que estás escribiendo en un input.
  • Solución: Aquí setState suele ser suficiente.

Es un estado que necesitan compartir varios widgets en diferentes partes de la App.

  • Ejemplos: Si el usuario está logueado, el contenido del carrito, el tema (oscuro/claro), las notificaciones no leídas.
  • Solución: Podemos elevar el estado o proporcionar un objeto a la parte del árbol que lo necesita.

El problema del prop drilling

La solución nativa “a lo bruto” para compartir datos se llama Elevar el Estado (Lifting State Up).

Consiste en mover la variable del carrito al widget “Abuelo” común a todas las pantallas (por ejemplo, al Main). Y desde ahí, ir pasando los datos hacia abajo a través de los constructores.

Esto genera el fenómeno conocido como Prop Drilling (Perforación de Propiedades) o “El Infierno de los Constructores”.

El escenario de pesadilla

Imagina que el widget Abuelo tiene el dato del Usuario. Y el widget Nieto necesita mostrar el nombre del usuario.

Para que el dato llegue, tiene que pasar por el Padre. Pero al Padre no le importa el usuario, no lo usa para nada. Solo actúa de tubería.

// 1. EL ABUELO (Tiene el dato)
class Abuelo extends StatelessWidget {
  final User usuario = User(name: "Luis");

  @override
  Widget build(BuildContext context) {
    // Tiene que pasárselo al padre...
    return Padre(usuario: usuario);
  }
}

// 2. EL PADRE (La tubería innecesaria)
class Padre extends StatelessWidget {
  final User usuario; // Recibe el dato AUNQUE NO LO USA
  
  const Padre({required this.usuario});

  @override
  Widget build(BuildContext context) {
    return Container(
      // ...y se lo pasa al nieto
      child: Nieto(usuario: usuario),
    );
  }
}

// 3. EL NIETO (Quien realmente lo necesita)
class Nieto extends StatelessWidget {
  final User usuario;
  
  const Nieto({required this.usuario});

  @override
  Widget build(BuildContext context) {
    return Text("Hola, ${usuario.name}");
  }
}
Copied!

Por qué puede ser un problema

  1. Código Sucio: Si tienes una jerarquía de 10 widgets, tienes que modificar 10 archivos para pasar una variable nueva.
  2. Acoplamiento: El widget Padre ahora depende de User. Si cambias el objeto User, tienes que tocar el Padre, aunque él ni lo pinte.
  3. Reconstrucciones: Si el dato cambia arriba, algunos widgets intermedios pueden volver a ejecutar build aunque no usen ese valor.

Proporcionar datos a un subárbol

Lo que realmente queremos es una forma de que el Abuelo tenga el dato, y el Nieto pueda acceder a él directamente sin molestar al Padre.

Queremos proporcionar una dependencia a los descendientes que la necesitan.

Queremos poder decir en cualquier parte de la App:

“Oye, ¿hay algún Carrito disponible por ahí arriba? Si es así, damelo.”

La solución: Provider

Para resolver esto usamos el ecosistema de State Management (Gestión de Estado). Hay muchas librerías (Riverpod, Bloc, GetX, MobX), pero Provider sigue siendo una opción muy buena para empezar porque la documentación oficial de Flutter lo usa como enfoque sencillo y enseña conceptos que luego aparecen en otras soluciones.

Provider es, en esencia, un envoltorio sobre una herramienta nativa de Flutter llamada InheritedWidget, pero mucho más fácil de usar.

Provider nos permite:

  1. Colocar un dato en la cima del árbol de widgets.
  2. Hacer que cualquier widget descendiente escuche ese dato.
  3. Si el dato cambia, se reconstruyen los widgets que se hayan suscrito a él, no toda la aplicación.