Start Debugging

Provider vs Riverpod vs Bloc para gestión de estado en Flutter en 2026

Elige Riverpod para la mayoría de apps nuevas de Flutter en 2026. Opta por Bloc si tu equipo grande quiere una estructura basada en eventos, y conserva Provider solo para código heredado.

Si vas a empezar una app nueva de Flutter en 2026 y no te decides entre Provider, Riverpod y Bloc, la respuesta corta es Riverpod. Con Riverpod 3.3.1 (la línea 3.0 salió el 2025-09-10) es seguro en tiempo de compilación, se puede probar sin un BuildContext, y la ruta de generación de código elimina casi todo el boilerplate que solía ser el principal argumento en su contra. Recurre a Bloc (flutter_bloc 9.1.1) cuando tengas un equipo grande que se beneficie de un contrato estricto basado en eventos y de un historial de estado rastreable. Conserva Provider (6.1.5) solo si ya lo tienes en una base de código o si le estás enseñando a alguien el modelo subyacente de InheritedWidget. Todos los ejemplos aquí usan Flutter 3.44 y Dart 3.12.

Los tres paquetes no son el mismo tipo de herramienta

Antes de compararlos, ayuda ver que estas bibliotecas resuelven problemas que se solapan pero son distintos.

Provider es un envoltorio fino y bien hecho sobre el propio InheritedWidget de Flutter. Hace inyección de dependencias y propagación de reconstrucciones, y poco más. La clase de estado suele ser un ChangeNotifier que escribes a mano. Es el paquete al que recurrió la documentación oficial de Flutter cuando necesitó un ejemplo didáctico, razón por la que tantos tutoriales lo usan.

Riverpod es lo que el autor de Provider construyó después, específicamente para arreglar los problemas estructurales de Provider: la ProviderNotFoundException en tiempo de ejecución, la dependencia de la posición del widget en el árbol y la imposibilidad de leer el estado desde Dart puro. Los providers de Riverpod viven fuera del árbol de widgets, así que son accesibles desde cualquier parte y se resuelven en tiempo de compilación.

Bloc es primero un patrón y luego un paquete. Te empuja a modelar cada cambio como un evento explícito que fluye hacia un componente y produce un nuevo estado inmutable. Esa ceremonia es justamente el punto: en un equipo grande, una tubería forzada Event -> Bloc -> State hace que el comportamiento sea predecible y revisable.

Matriz de características

CaracterísticaProvider 6.1.5Riverpod 3.3.1Bloc 9.1.1
Modelo mentalInheritedWidget + ChangeNotifierProviders fuera del árbolBasado en eventos, estados inmutables
Seguridad en tiempo de compilaciónNo (búsqueda en tiempo de ejecución)
Necesita BuildContext para leerNoNo (vía context.read o directo)
BoilerplateBajoBajo con codegenAlto
TestabilidadRequiere montar el widgetDart puro, sin árbol de widgetsDart puro, ayudantes bloc_test
Estado async / de cargaManualAsyncValue, integradoEstados manuales o emit
Reintento automático ante fallosNoSí (desde 3.0)No
Trazabilidad del estadoDébilMediaFuerte (cada transición observable)
Curva de aprendizajeSuaveModeradaPronunciada
Mejor encajeHeredado, tutorialesLa mayoría de apps nuevasEquipos grandes, flujos complejos

La fila más importante es “seguridad en tiempo de compilación”. Un Provider mal configurado lanza ProviderNotFoundException en tiempo de ejecución, a menudo solo en la pantalla donde falta. Riverpod y Bloc sacan a la luz esa clase de error antes de que la app se ejecute.

El mismo contador en los tres

Un contador es lo bastante pequeño para comparar la ergonomía directamente. Observa cuánto código necesita cada uno y dónde vive el estado.

Provider

// Flutter 3.44, provider 6.1.5
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class CounterModel extends ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

// Register it above the widgets that need it.
ChangeNotifierProvider(
  create: (_) => CounterModel(),
  child: const CounterPage(),
);

class CounterPage extends StatelessWidget {
  const CounterPage({super.key});

  @override
  Widget build(BuildContext context) {
    final count = context.watch<CounterModel>().count;
    return Scaffold(
      body: Center(child: Text('$count')),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.read<CounterModel>().increment(),
        child: const Icon(Icons.add),
      ),
    );
  }
}

Fíjate en la dependencia de context. Si CounterPage se renderiza sin un ChangeNotifierProvider por encima, context.watch<CounterModel>() lanza en tiempo de ejecución.

Riverpod

// Flutter 3.44, flutter_riverpod 3.3.1, riverpod_annotation
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'counter.g.dart';

@riverpod
class Counter extends _$Counter {
  @override
  int build() => 0;

  void increment() => state++;
}

class CounterPage extends ConsumerWidget {
  const CounterPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);
    return Scaffold(
      body: Center(child: Text('$count')),
      floatingActionButton: FloatingActionButton(
        onPressed: () => ref.read(counterProvider.notifier).increment(),
        child: const Icon(Icons.add),
      ),
    );
  }
}

El counterProvider se genera y es accesible globalmente. No hay una posición en el árbol que equivocar, y ref se resuelve en tiempo de compilación. Envuelve la app una vez en ProviderScope y listo.

Bloc

// Flutter 3.44, flutter_bloc 9.1.1, equatable
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

// Events
sealed class CounterEvent {}
class Increment extends CounterEvent {}

// Bloc: Event in, int state out.
class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0) {
    on<Increment>((event, emit) => emit(state + 1));
  }
}

class CounterPage extends StatelessWidget {
  const CounterPage({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => CounterBloc(),
      child: Builder(
        builder: (context) => Scaffold(
          body: Center(
            child: BlocBuilder<CounterBloc, int>(
              builder: (context, count) => Text('$count'),
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () => context.read<CounterBloc>().add(Increment()),
            child: const Icon(Icons.add),
          ),
        ),
      ),
    );
  }
}

Bloc es el más verboso para un contador, y esa comparación es injusta con él: el valor de Bloc aparece cuando hay veinte eventos y diez estados, no uno. El evento Increment es un registro en el historial de tu app. Con un BlocObserver adjunto puedes registrar cada transición, que es exactamente lo que quieres al depurar una pantalla compleja.

Cuándo elegir Riverpod

Una advertencia: en Riverpod 3.0, StateProvider, StateNotifierProvider y ChangeNotifierProvider se movieron a package:riverpod/legacy.dart. El código nuevo debería usar las clases Notifier y AsyncNotifier mostradas arriba, idealmente con generación de código mediante @riverpod.

Cuándo elegir Bloc

Cuándo elegir Provider

Lo que Provider no debería ser en 2026 es la opción por defecto para una app nueva y no trivial. El modelo de búsqueda en tiempo de ejecución es justo el problema que Riverpod fue construido para eliminar.

Las pegas que deciden por ti

Algunas restricciones se imponen a la preferencia personal.

Leer el estado desde código que no es widget. Si tu arquitectura tiene una capa de servicio o repositorio que debe leer el estado de la app directamente, Provider queda prácticamente descartado. Necesita un BuildContext. Riverpod y Bloc permiten leer el estado desde Dart puro, lo que normalmente zanja la decisión por sí solo.

Tamaño del equipo y cultura de revisión. En un proyecto en solitario o un equipo pequeño, la ceremonia de Bloc es fricción con poca recompensa, y Riverpod gana en velocidad. En un equipo de 15 personas donde la consistencia entre funcionalidades importa más que las líneas de código, la rigidez de Bloc es una ventaja, no un costo.

Disciplina de estado inmutable. Tanto Bloc como el Riverpod moderno te empujan hacia objetos de estado inmutables. Si tu equipo se siente cómodo con clases selladas e igualdad por valor (mira Dart records vs clases Freezed para las opciones de modelado), ambos encajan. Si tienes una base de código grande construida sobre objetos ChangeNotifier mutables, la ruta más barata puede ser quedarte en Provider hasta que una funcionalidad realmente necesite más.

Patrones async existentes. El AsyncValue de Riverpod es la forma de menor esfuerzo para renderizar carga, datos y error desde una única fuente de verdad. Si tus pantallas son en su mayoría obtenciones de datos async, eso solo ya es una razón fuerte para elegirlo.

La recomendación, repetida

Para la mayoría de apps nuevas de Flutter en 2026, usa Riverpod 3.3.1 con generación de código. Obtienes seguridad en tiempo de compilación, lecturas sin contexto, async de primera clase y las características de resiliencia de 3.0, a un costo de boilerplate que codegen acerca al de Provider.

Elige Bloc 9.1.1 cuando una estructura forzada basada en eventos y un historial de estado totalmente rastreable valgan más para tu equipo que la concisión, lo cual suele ser cierto en equipos grandes y flujos complejos. Usa Cubits dentro de una app Bloc para las pantallas que no necesiten eventos completos.

Conserva Provider 6.1.5 para apps heredadas, enseñanza y pantallas triviales, pero no lo elijas por defecto en un proyecto nuevo y no trivial. La pregunta decisiva rara vez es “cuál tiene el contador más bonito”, es “¿leo estado fuera de los widgets, qué tan grande es mi equipo y cuánto async tengo?”. Responde esas tres y la elección suele hacerse sola. Si estás sopesando Flutter frente a otros stacks por completo, nuestra comparación de Flutter vs React Native vs MAUI aleja la vista un nivel. Y elijas lo que elijas, recuerda liberar tus controllers, porque ninguna biblioteca de gestión de estado los limpiará por ti.

Fuentes

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Volver