¿Qué es Provider y por qué usarlo en lugar de setState?
En un artículo anterior, exploramos la gestión de estado con setState, un método integrado en Flutter que es perfecto para aplicaciones simples o cambios de estado locales. Sin embargo, cuando tu aplicación comienza a crecer, con múltiples pantallas, widgets anidados y datos compartidos entre diferentes partes de la interfaz, setState muestra rápidamente sus limitaciones. La gestión manual de las actualizaciones se vuelve complicada, el código pierde claridad y el rendimiento puede verse afectado debido a reconstrucciones innecesarias.
Aquí es donde entra en juego Provider, una solución popular y ligera para la gestión de estado en Flutter. Recomendado por el equipo de Flutter, Provider es una biblioteca que simplifica el intercambio y la actualización de datos en una aplicación, manteniendo su eficiencia y facilidad de comprensión. A diferencia de setState, que requiere reconstruir manualmente los widgets, Provider adopta un enfoque reactivo: notifica automáticamente a los widgets afectados cuando un estado cambia, sin afectar a los demás. Para aplicaciones complejas, esto significa un código más limpio, mejor escalabilidad y un mantenimiento simplificado.
¿En qué es más eficiente Provider que setState?
Veamos un ejemplo sencillo para entender por qué Provider es más eficiente que setState para conservar los valores de tus variables a lo largo de tu aplicación.
Supongamos que tienes un botón que cuenta cuántas veces haces clic en él y usa setState para actualizar el número después de cada clic. Este método funciona bien en una sola página, pero se vuelve complicado si deseas mostrar ese número en otra página. Tendrás que encontrar una forma de pasarlo a través de tus widgets, lo que puede volverse difícil rápidamente.
class Contador extends StatefulWidget {
@override
_ContadorState createState() => _ContadorState();
}
class _ContadorState extends State<Contador> {
int numero = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Número: $numero'),
ElevatedButton(
onPressed: () {
setState(() {
numero++;
});
},
child: Text('Haz clic en mí'),
),
],
);
}
}
Ahora, hagamos lo mismo, pero con Provider. En lugar de mantener el valor dentro de un widget, lo colocarás en una clase especial (como una caja de herramientas) para que todos tus widgets puedan acceder a él. Cuando el número cambia, Provider notifica automáticamente a los widgets que lo usan y lo actualiza.
// La caja de herramientas
class ContadorManager extends ChangeNotifier {
int numero = 0;
void incrementar() {
numero++;
notifyListeners(); // Notifica a los widgets: "¡Hey, he cambiado!"
}
}
// En tu aplicación
ElevatedButton(
onPressed: () {
Provider.of<ContadorManager>(context, listen: false).incrementar();
},
child: Text('Haz clic en mí'),
)
// Y en otro lugar, para mostrar el número
Consumer<ContadorManager>(
builder: (context, contador, child) {
return Text('Número: ${contador.numero}');
},
)
¿Cómo implementar Provider en tu aplicación Flutter?
Para entender cómo configurar Provider, imaginemos una aplicación con una página que tiene un campo de texto donde el usuario escribe una palabra y una lista que se actualiza en tiempo real con las palabras enviadas. Con setState, esto requeriría pasar callbacks o manejar estados manualmente a través de los widgets. Con Provider, todo se vuelve más fluido. Aquí tienes los pasos para configurarlo.
Paso 1: Agregar la dependencia de Provider
Primero, asegúrate de agregar el package provider (6.1.2) a tu proyecto. En tu archivo pubspec.yaml
, incluye:
dependencies:
provider: ^6.1.2
Luego, ejecuta flutter pub get
para instalar la dependencia.
Paso 2: Crear una clase de gestión de estado
Provider se basa en una clase que contiene los datos y la lógica de tu aplicación. Llamaremos a esta clase WordManager
. Hereda de ChangeNotifier
, lo que le permite notificar a los widgets cuando ocurre un cambio.
import 'package:flutter/material.dart';
class WordManager extends ChangeNotifier {
String _currentWord = '';
List<String> _wordList = [];
String get currentWord => _currentWord;
List<String> get wordList => _wordList;
void updateWord(String newWord) {
_currentWord = newWord;
notifyListeners(); // Notifica a los widgets suscritos al cambio
}
void addWord() {
if (_currentWord.isNotEmpty) {
_wordList.add(_currentWord);
_currentWord = ''; // Reinicia el campo después de agregar
notifyListeners();
}
}
}
Aquí, WordManager
maneja dos variables: _currentWord
(la palabra que se está escribiendo) y _wordList
(la lista de palabras). Cada vez que estos datos cambian, notifyListeners()
informa a los widgets dependientes de este estado.
Paso 3: Integrar Provider en el árbol de widgets
Para hacer que este estado sea accesible en toda la aplicación, coloca un ChangeNotifierProvider
en la parte superior de tu árbol de widgets, generalmente en main.dart
.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'word_manager.dart';
import 'home_page.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => WordManager(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Demo Provider',
home: HomePage(),
);
}
}
El ChangeNotifierProvider
encapsula tu aplicación y proporciona una instancia de WordManager
a todos los widgets descendientes.
Paso 4: Construir la interfaz con Provider
Ahora crearemos una página (HomePage
) con un campo de texto y una lista. Los widgets escucharán los cambios a través de Provider.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'word_manager.dart';
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Gestión con Provider')),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
children: [
// Campo de texto
TextField(
onChanged: (value) {
Provider.of<WordManager>(context, listen: false).updateWord(value);
},
decoration: InputDecoration(labelText: 'Ingresa una palabra'),
),
SizedBox(height: 20),
// Mostrar la palabra en tiempo real
Consumer<WordManager>(
builder: (context, wordManager, child) {
return Text('Palabra actual: ${wordManager.currentWord}');
},
),
SizedBox(height: 20),
// Botón para agregar a la lista
ElevatedButton(
onPressed: () {
Provider.of<WordManager>(context, listen: false).addWord();
},
child: Text('Agregar a la lista'),
),
SizedBox(height: 20),
// Lista de palabras
Expanded(
child: Consumer<WordManager>(
builder: (context, wordManager, child) {
return ListView.builder(
itemCount: wordManager.wordList.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(wordManager.wordList[index]),
);
},
);
},
),
),
],
),
),
);
}
}
Explicación del código
Provider.of
: Se usa para acceder aWordManager
y llamar a sus métodos (comoupdateWord
oaddWord
). La opciónlisten: false
indica que no queremos reconstruir el widget en ese punto, solo actuar sobre el estado.Consumer
: Este widget escucha los cambios enWordManager
y solo reconstruye las partes necesarias (por ejemplo, el texto o la lista) cuandonotifyListeners()
es llamado.
Modificar una variable con Provider
Con Provider, modificar el estado es tan sencillo como cambiar variables en una clase, como harías con setState
. La principal diferencia es que todo está centralizado en tu ChangeNotifier
. Aquí tienes algunas operaciones básicas:
Modificar un valor simple
Aquí tienes cómo modificar un contador con Provider:
class CounterManager extends ChangeNotifier {
int count = 0;
void increment() {
count++; // Cambia el valor
notifyListeners(); // Notifica a los widgets
}
}
Puedes luego llamar la función con:
Provider.of<CounterManager>(context, listen: false).increment();
Agregar un elemento a una lista
class ListManager extends ChangeNotifier {
List<String> items = [];
void addItem(String item) {
items.add(item); // Agrega a la lista
notifyListeners(); // Actualiza la pantalla
}
}
Eliminar un elemento de una lista
void removeItem(String item) {
items.remove(item); // Elimina el elemento
notifyListeners(); // Notifica a los widgets
}
Multiplicar un valor
void multiplyCount(int factor) {
count *= factor; // Multiplica
notifyListeners(); // Actualiza
}
Conclusión
Provider es una manera mucho más eficiente de gestionar todas las variables que tu aplicación necesita para funcionar y es un método recomendado por el equipo de Flutter. Sin embargo, una nueva solución aún más eficiente está comenzando a ganar popularidad: Riverpod.