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:
- La pantalla de Catálogo (para mostrar un contador “3 items”).
- La pantalla de Detalle de Producto (para añadir más).
- 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í
setStatesuele 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}");
}
}
Por qué puede ser un problema
- Código Sucio: Si tienes una jerarquía de 10 widgets, tienes que modificar 10 archivos para pasar una variable nueva.
- Acoplamiento: El widget
Padreahora depende deUser. Si cambias el objetoUser, tienes que tocar elPadre, aunque él ni lo pinte. - Reconstrucciones: Si el dato cambia arriba, algunos widgets intermedios pueden volver a ejecutar
buildaunque 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:
- Colocar un dato en la cima del árbol de widgets.
- Hacer que cualquier widget descendiente escuche ese dato.
- Si el dato cambia, se reconstruyen los widgets que se hayan suscrito a él, no toda la aplicación.