Riverpod — Gestión Compilada de Dependencias para Flutter

Autor: IT Sectr Publicado: 2026-02-19 Tiempo de lectura: 7 min

Riverpod — un gestor de estado y dependencias compilado para Flutter, creado por Remi Rousselet en 2021 como sucesor de Provider. Riverpod resuelve problemas fundamentales de Provider: falta de verificación en tiempo de compilación, dependencia de BuildContext y complejidad con ProviderNotFoundException. Según pub.dev, el paquete tiene más de 5 mil likes y está reemplazando activamente a Provider en nuevos proyectos.

Puntos Clave

  • ProviderRef — un objeto para acceder a otros providers dentro de un provider
  • AsyncValue — un envoltorio para datos asíncronos con estados loading/error/data
  • Notifier — una clase para estado mutable con métodos de mutación
  • ProviderScope — el widget raíz que gestiona todos los providers
  • Code Generation — anotaciones @riverpod para generación automática de providers

¿Qué es Riverpod?

Riverpod es una biblioteca de gestión de estado e inyección de dependencias para Flutter que compila descripciones de providers en código Dart seguro. A diferencia de Provider, los providers de Riverpod no están vinculados a BuildContext: se crean globalmente o en ProviderScope y son accesibles desde cualquier lugar. El compilador verifica tipos, dependencias y la integridad del grafo de providers en tiempo de compilación, eliminando errores en tiempo de ejecución como ProviderNotFoundException.

Riverpod utiliza el modelo override para pruebas: cada provider puede ser sobrescrito mediante ProviderScope.overrideWith sin necesidad de crear subclases o simular interfaces. Esto hace que las pruebas sean aisladas: cada prueba obtiene su propia copia del grafo de dependencias que se controla completamente.

Según la Flutter Community Survey 2025, Riverpod ocupa el tercer lugar en popularidad después de Provider y BLoC. Sin embargo, Riverpod es el paquete de más rápido crecimiento: +120% de instalaciones en 2024. Las razones principales: seguridad en tiempo de compilación, sin ProviderNotFoundException, soporte asíncrono integrado a través de AsyncValue.

Tipos de Providers

Riverpod proporciona 8 tipos de providers, cada uno para un escenario específico: Provider (constante/servicio), StateProvider (estado primitivo), StateNotifierProvider (lógica compleja con StateNotifier), ChangeNotifierProvider (para migración desde Provider), FutureProvider (datos asíncronos, una vez), StreamProvider (flujo reactivo), NotifierProvider (nueva API, Flutter 3.10+) y AsyncNotifierProvider (Notifier asíncrono).

Dart
final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {
  return CounterNotifier();
});

class CounterNotifier extends StateNotifier<int> {
  CounterNotifier() : super(0);

  void increment() => state++;
  void decrement() => state--;
}

class CounterScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);
    return Text('$count');
  }
}

ProviderRef — un objeto pasado a cada provider para acceder a otros providers. ref.watch — suscripción a cambios, ref.read — lectura única, ref.invalidate — reinicio de caché. ProviderRef reemplaza BuildContext de Provider: cualquier provider puede leer otros providers sin acceso al árbol de widgets. Esto permite construir un grafo de dependencias fuera de la capa de UI.

ProviderScope — el widget raíz, obligatorio para que Riverpod funcione. ProviderScope almacena todos los providers, gestiona su ciclo de vida y almacena en caché los valores. Sin ProviderScope la aplicación fallará con ProviderNotFoundException. ProviderScope puede anidarse — un ámbito anidado sobrescribe los providers del padre, lo que se usa para pruebas y aislamiento de características.

AsyncValue y Trabajo con Asincronía

AsyncValue — una clase sellada de Riverpod para representar estado asíncrono. AsyncValue tiene tres variantes: AsyncData (datos exitosos), AsyncError (error), AsyncLoading (cargando). En lugar de cambiar manualmente entre loading/error/data, cada FutureProvider o StreamProvider devuelve automáticamente AsyncValue, y el widget maneja los tres estados a través de ref.watch.

Dart
final userProvider = FutureProvider((ref) async {
  final api = ref.watch(apiProvider);
  return await api.fetchUser();
});

class UserScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final userAsync = ref.watch(userProvider);
    return userAsync.when(
      data: (user) => UserWidget(user),
      error: (e, _) => ErrorWidget(e.toString()),
      loading: () => CircularProgressIndicator(),
    );
  }
}

AsyncValue.when — un método para hacer pattern-matching de los tres estados. El compilador verifica que los tres casos sean manejados — si olvidas loading o error, el código no compilará. AsyncValue.whenData — solo para data (si loading/error no son necesarios). AsyncValue.guard — un envoltorio sobre try-catch para convertir excepciones en AsyncError. keepAlive — una bandera que evita que el caché del provider se destruya cuando sale del ámbito.

Generación de Código y @riverpod

Generación de código — una característica clave de Riverpod 2.0+. La anotación @riverpod sobre una función genera automáticamente un provider con el tipo correcto, soporte de refactorización y autocompletado. La generación de código utiliza riverpod_generator y build_runner. El desarrollador escribe una función pura, y todo lo demás — tipos, clases, constructores factory — se genera automáticamente.

Dart
@riverpod
String helloWorld(HelloWorldRef ref) {
  return 'Hello World';
}

// Generado: final helloWorldProvider = Provider((ref) => 'Hello World');

@riverpod
class Counter extends _$Counter {
  int build() => 0;
  void increment() => state++;
}

Notifier — la nueva API para estado mutable con generación de código. Notifier es una clase con un método build() y métodos de mutación de estado. A diferencia de StateNotifier, Notifier no requiere una clase de estado separada y proporciona acceso directo a state mediante getter/setter. Riverpod genera automáticamente un NotifierProvider para cada clase Notifier anotada con @riverpod.

build_runner: la generación de código se ejecuta con el comando dart run build_runner build. Los archivos generados tienen el sufijo .g.dart y se importan en el código fuente. Cuando cambian las anotaciones o los tipos de providers, es necesario volver a ejecutar la generación de código. Riverpod 2.x recomienda la generación de código para todos los proyectos nuevos — la creación manual de providers se está volviendo obsoleta.

Riverpod vs Provider

Diferencias clave entre Riverpod y Provider: independencia de BuildContext, seguridad en tiempo de compilación, soporte asíncrono integrado, almacenamiento automático en caché y pruebas mediante override. Provider requiere BuildContext para acceder al estado (context.watch, context.read), Riverpod usa WidgetRef y providers declarados globalmente.

CaracterísticaProviderRiverpod
Dependencia de BuildContextNo
Verificación en compilaciónNoSí (mediante @riverpod)
ProviderNotFoundExceptionRuntimeImposible
AsincroníaManualAsyncValue (integrado)
PruebasEnvoltorio en ProviderProviderScope.overrideWith
Almacenamiento en cachéNoAutomático + keepAlive

Migración desde Provider: Riverpod soporta ChangeNotifierProvider.adaptive para usar ChangeNotifier existente sin reescribir. Migración gradual: primero las nuevas funciones se escriben con Riverpod, luego las instancias antiguas de Provider se reemplazan con providers de Riverpod mediante un adaptador. Ambos paquetes pueden coexistir en un mismo proyecto, lo que permite migrar sin congelar el desarrollo.

Pruebas de Riverpod

Las pruebas de Riverpod se basan en ProviderScope.overrideWith. Cada provider se sobrescribe dentro de un ProviderScope de prueba sin mocks ni contenedores DI. ProviderContainer — un entorno aislado para pruebas sin Flutter (Dart puro), que permite probar providers sin renderizar widgets.

Dart
import 'package:flutter_test/flutter_test.dart';
import 'package:riverpod/riverpod.dart';

void main() {
  test('Counter increments correctly', () {
    final container = ProviderContainer();
    container.read(counterProvider.notifier).increment();
    expect(container.read(counterProvider), 1);
  });

  testWidgets('UI updates on increment', (tester) async {
    await tester.pumpWidget(
      ProviderScope(
        overrides: [counterProvider.overrideWithValue(5)],
        child: CounterScreen(),
      ),
    );
    expect(find.text('5'), findsOneWidget);
  });
}

ProviderContainer — sin Flutter. Use ProviderContainer para pruebas unitarias de providers sin widgets. overrideWithValue — reemplazar un provider con un valor específico. overrideWith — reemplazar con una fábrica de providers (para simular servicios). autodispose — en las pruebas verifique que el provider se destruye cuando sale del ámbito usando container.dispose().

Preguntas Frecuentes

¿En qué se diferencia Riverpod de BLoC?

Riverpod es una biblioteca de gestión de estado con providers globales, AsyncValue y generación de código. BLoC es un patrón arquitectónico con Event → Stream → State. Riverpod es más fácil de aprender y tiene mejor experiencia de desarrollo mediante anotaciones @riverpod. BLoC proporciona un estricto aislamiento de la lógica de negocio y trazabilidad de Eventos a través de BlocObserver. La elección depende de la paradigma del proyecto: Riverpod está más cerca de Provider, BLoC — de los flujos reactivos.

¿Qué es autodispose en Riverpod?

Autodispose es un mecanismo para destruir automáticamente un provider cuando nadie está suscrito a él. Por defecto, todos los providers de Riverpod hacen autodispose: cuando un widget sale del árbol, el provider se elimina de la memoria. keepAlive — una bandera que deshabilita autodispose para providers que deben vivir siempre (clientes API, repositorios, configuraciones). Esto evita fugas de memoria — los providers no utilizados se destruyen automáticamente.

¿Cómo funciona ref.invalidate?

ref.invalidate — un método que restablece forzosamente el caché del provider. Después de invalidate, el provider se recrea en la siguiente lectura: FutureProvider vuelve a ejecutar la función asíncrona, StreamProvider se vuelve a suscribir al flujo. Use invalidate para forzar la actualización de datos (pull-to-refresh, cambio de usuario). ref.refresh — una combinación de invalidate + lectura: restablece y lee inmediatamente el nuevo valor en una sola operación.

¿Se puede usar Riverpod sin generación de código?

Sí. Riverpod 1.x funciona solo sin generación de código — los providers se crean manualmente usando Provider(), StateNotifierProvider(), FutureProvider(), etc. Riverpod 2.x soporta ambos enfoques. Sin generación de código hay más código repetitivo pero ninguna dependencia de build_runner y dart run build_runner build. Para proyectos pequeños (hasta 30 providers), la creación manual está justificada; para los grandes, la generación de código es obligatoria.

¿Qué son los providers Family?

Family — un modificador de provider que acepta un parámetro externo. Por ejemplo, userProvider(123) — un provider que carga un usuario con ID 123. Los providers Family almacenan en caché el resultado para cada parámetro único por separado. Use Family para listas de elementos donde cada elemento se carga por ID. El modificador Family está disponible para todos los tipos de providers: Provider.family, FutureProvider.family, StreamProvider.family.

Resumen

  • Riverpod — un gestor de estado compilado, sucesor de Provider sin ProviderNotFoundException
  • ProviderRef — un reemplazo de BuildContext para acceder a providers dentro de otros providers
  • AsyncValue — una clase sellada con estados loading/error/data para datos asíncronos
  • Generación de código @riverpod — inferencia automática de tipos y fábricas de providers
  • ProviderScope.overrideWith — pruebas aisladas sin mocks ni contenedores DI
  • Family — providers parametrizados con almacenamiento en caché individual
  • autodispose y keepAlive — gestión automática del ciclo de vida de los providers

Desarrollaremos una aplicación móvil llave en mano

IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.

Discutir el proyecto

Lea también