¿Cuál es la diferencia entre Provider y Riverpod en Flutter?
En un artículo anterior, exploramos la gestión del estado con Provider, que simplifica el compartir datos en una aplicación Flutter en comparación con setState. Provider es excelente para proyectos de tamaño medio, ofreciendo un enfoque reactivo y una buena escalabilidad. Sin embargo, tiene algunas limitaciones:
- Gestionar muchos datos conectados se convierte en un dolor de cabeza: Imagina que tu aplicación tiene que trabajar con varios Providers. Por ejemplo, una clase para contar los clics y otra para mostrar un mensaje según ese número. Esto sigue siendo factible si no hay demasiados Providers para gestionar al mismo tiempo, pero puede volverse más complejo a medida que el proyecto crece. Riverpod, actúa como el director de orquesta y organiza todo eso sin que tengas que hacer nada.
- Provider está demasiado vinculado al «contexto» (BuildContext): Imagina que tu aplicación es una casa con muchas habitaciones (widgets). Con Provider (que actúa como la caja de herramientas), para acceder al estado de tus datos, siempre tienes que saber en qué habitación estás, eso es lo que se llama el BuildContext. Es como si tuvieras que gritar constantemente «¿Dónde estoy?» para tomar tus herramientas. Esta solución no es demasiado restrictiva en la mayoría de los casos, pero puede volverse engorrosa al probar código o mover cosas con facilidad. Riverpod, por su parte, da acceso directo a la caja de herramientas sin pedir el contexto.
- Escribir código con Provider puede volverse largo y repetitivo: Con Provider, a menudo tendrás que usar métodos como
Consumer
oProvider.of
para indicar a la aplicación que un dato ha cambiado y necesita ser actualizado. Esto no es tan problemático en un proyecto de tamaño medio, pero puede agregar muchas más líneas de código en proyectos más complejos.
Ventajas de Riverpod sobre Provider
Para entender mejor las ventajas de Riverpod sobre Provider, tomemos como ejemplo un botón que cuenta el número de veces que se hace clic sobre él. Con Provider, se vería así:
// Clase para gestionar el estado
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class CounterManager extends ChangeNotifier {
int count = 0;
void increment() {
count++;
notifyListeners(); // Notificar a los widgets
}
}
// main.dart
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => CounterManager(),
child: MaterialApp(home: CounterPage()),
),
);
}
// counter_page.dart
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Consumer<CounterManager>(
builder: (context, counter, _) => Text('Número: ${counter.count}'),
),
ElevatedButton(
onPressed: () => Provider.of<CounterManager>(context, listen: false).increment(),
child: Text('¡Haz clic!'),
),
],
),
),
);
}
}
Y ahora, aquí está el mismo código, pero utilizando Riverpod para la gestión del estado:
// counter_provider.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
final counterProvider = StateProvider<int>((ref) => 0);
// main.dart
void main() {
runApp(ProviderScope(child: MaterialApp(home: CounterPage())));
}
// counter_page.dart
class CounterPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Número: $count'),
ElevatedButton(
onPressed: () => ref.read(counterProvider.notifier).state++,
child: Text('¡Haz clic!'),
),
],
),
),
);
}
}
Como puedes ver, las ventajas de Riverpod sobre Provider son:
- Menos código: No es necesario crear una clase completa como con Provider. Un simple
StateProvider
es suficiente. - Más directo: Con Riverpod, accedes y modificas el estado en una línea (
ref.watch
yref.read
), sin pasar porConsumer
ninotifyListeners()
. - Menos complicaciones: Provider depende del
BuildContext
y puede ser pesado de manejar en una gran aplicación. Riverpod, por su parte, es independiente y va directo al grano.
¿Cómo configurar Riverpod para tu aplicación Flutter?
Para entender cómo configurar Riverpod, imaginemos una aplicación que tiene una página con un campo de texto donde el usuario escribe una palabra, y una lista que se actualiza en tiempo real con las palabras enviadas.
Paso 1: Añadir la dependencia Riverpod
Primero, asegúrate de añadir el paquete flutter_riverpod
a tu proyecto. En tu archivo pubspec.yaml
, incluye:
dependencies:
flutter_riverpod: ^2.5.1
Luego, ejecuta flutter pub get
para instalar la dependencia.
Paso 2: Crear los providers para gestionar el estado
Riverpod utiliza «providers» para almacenar y gestionar los datos de tu aplicación. Vamos a crear dos providers: uno para la palabra actual y otro para la lista de palabras.
import 'package:flutter_riverpod/flutter_riverpod.dart';
// Provider para la palabra actual
final currentWordProvider = StateProvider<String>((ref) => '');
// Provider para la lista de palabras
final wordListProvider = StateProvider<List<String>>((ref) => []);
Aquí, currentWordProvider
contiene la palabra que estás escribiendo, y wordListProvider
guarda la lista de palabras añadidas. Estos providers actúan como cajas de herramientas accesibles en toda la aplicación, y Riverpod se encarga de actualizarlos automáticamente.
Paso 3: Integrar Riverpod en el árbol de widgets
Para que estos providers sean utilizables, coloca un ProviderScope
en la parte superior de tu árbol de widgets, generalmente en main.dart
.
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'word_page.dart';
void main() {
runApp(
ProviderScope(
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Demo Riverpod',
home: WordPage(),
);
}
}
Paso 4: Construir la interfaz con Riverpod
Crea una página (WordPage
) con un campo de texto y una lista. Los widgets escucharán los cambios a través de Riverpod.
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'word_provider.dart';
class WordPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final currentWord = ref.watch(currentWordProvider); // Escucha la palabra actual
final wordList = ref.watch(wordListProvider); // Escucha la lista
return Scaffold(
appBar: AppBar(title: Text('Gestión con Riverpod')),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
children: [
// Campo de texto
TextField(
onChanged: (value) {
ref.read(currentWordProvider.notifier).state = value;
},
decoration: InputDecoration(labelText: 'Ingresa una palabra'),
),
SizedBox(height: 20),
// Mostrar la palabra en tiempo real
Text('Palabra actual: $currentWord'),
SizedBox(height: 20),
// Botón para añadir a la lista
ElevatedButton(
onPressed: () {
if (currentWord.isNotEmpty) {
ref.read(wordListProvider.notifier).state = [...wordList, currentWord];
ref.read(currentWordProvider.notifier).state = '';
}
},
child: Text('Añadir a la lista'),
),
SizedBox(height: 20),
// Lista de palabras
Expanded(
child: ListView.builder(
itemCount: wordList.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(wordList[index]),
);
},
),
),
],
),
),
);
}
}
Explicaciones del código
ref.watch
: Se utiliza para escuchar los providers (currentWordProvider
ywordListProvider
). Cuando sus valores cambian, los widgets afectados (el texto y la lista) se actualizan automáticamente.ref.read
: Permite modificar los providers sin escucharlos. Por ejemplo, actualizamos la palabra actual o agregamos una palabra a la lista..notifier.state
da acceso al valor para cambiarlo directamente.
¿Qué propiedades usar con Riverpod para modificar el estado?
Con Riverpod, puedes modificar el estado de tus providers de diferentes maneras, dependiendo del tipo de dato (número, lista, cadena, etc.) y lo que quieras hacer (añadir, eliminar, modificar).
Entre los métodos que más utilizarás están:
ref.read
: Da acceso al provider para modificarlo..notifier
: Es como el «control remoto» del provider..state
: Permite notificar a Riverpod que un valor ha cambiado y avisa a los widgets que escuchan conref.watch
.
Aquí algunos ejemplos simples con StateProvider
:
Modificar un valor simple (número, cadena, booleano)
final numberProvider = StateProvider<int>((ref) => 0);
// Incrementar un número
ref.read(numberProvider.notifier).state += 1;
//O para una cadena
final nameProvider = StateProvider<String>((ref) => 'Anna');
ref.read(nameProvider.notifier).state = 'Bob';
Agregar un elemento a una lista
Para agregar algo a una lista:
final numbersProvider = StateProvider<List<int>>((ref) => [1, 2, 3]);
// Agregar 4 a la lista
ref.read(numbersProvider.notifier).state = [...ref.read(numbersProvider.notifier).state, 4];
// Resultado: [1, 2, 3, 4]
Eliminar un elemento de una lista
Para eliminar un elemento:
final itemsProvider = StateProvider<List<String>>((ref) => ['manzana', 'pera']);
// Eliminar "manzana"
ref.read(itemsProvider.notifier).state = ref.read(itemsProvider.notifier).state.where((item) => item != 'manzana').toList();
// Resultado: ['pera']
Multiplicar o transformar un valor
Para modificar un valor con un cálculo:
final scoreProvider = StateProvider<int>((ref) => 5);
// Multiplicar por 10
ref.read(scoreProvider.notifier).state *= 10;
// Resultado: 50
Vaciar o reiniciar
Para reiniciar:
final tasksProvider = StateProvider<List<String>>((ref) => ['tarea1', 'tarea2']);
// Vaciar la lista
ref.read(tasksProvider.notifier).state = [];
// Resultado: []