Estado en Flutter en 2026: Riverpod 2, Bloc y cuándo usar cada uno

La gestión de estado es uno de esos temas en Flutter donde hay opiniones para todos los gustos. En 2026, la discusión práctica se reduce a dos opciones principales: Riverpod 2 y Bloc. setState sigue existiendo y tiene su sitio, pero para cualquier cosa que supere un par de pantallas, necesitas algo más estructurado.

setState: cuándo sí y cuándo no

Antes de entrar en Riverpod y Bloc, conviene no olvidar que setState es perfectamente válido para estado local de un widget: un interruptor que se activa, un formulario que muestra u oculta campos, una animación que se dispara al pulsar un botón. El problema llega cuando intentas compartir ese estado entre widgets que no están en la misma rama del árbol.

Si te encuentras pasando callbacks y datos por tres o cuatro niveles de widgets, es señal de que hace falta una solución de estado más explícita.

Riverpod 2: el sucesor de Provider

Riverpod nació como solución a los problemas de Provider: dependencias del árbol de widgets, acceso desde fuera del árbol, y ciertos problemas con el tipo de BuildContext. Riverpod 2.x, con soporte para code generation, es hoy la versión recomendada.

El núcleo de Riverpod son los providers: objetos inmutables que se declaran fuera del widget y que Riverpod gestiona. Los tipos más habituales son:

  • Provider: para valores síncronos que no cambian.
  • StateNotifierProvider / NotifierProvider: para estado mutable con lógica encapsulada.
  • FutureProvider: para operaciones asíncronas.
  • StreamProvider: para streams de datos.
// Con code generation (riverpod_annotation)
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'counter.g.dart';

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

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

// En el widget
class CounterWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);
    return Column(
      children: [
        Text('$count'),
        ElevatedButton(
          onPressed: () => ref.read(counterProvider.notifier).increment(),
          child: const Text('+'),
        ),
      ],
    );
  }
}

El code generation con @riverpod elimina bastante boilerplate y hace que el análisis estático funcione mejor. Si empiezas un proyecto nuevo, úsalo desde el principio.

Una de las ventajas de Riverpod es que los providers se pueden combinar con ref.watch: un provider puede depender del valor de otro, y Riverpod recalcula automáticamente cuando cambia la dependencia. Esto simplifica mucho la lógica de filtros, búsquedas y paginación.

Bloc: eventos, estados y flujo explícito

Bloc (y su variante más sencilla, Cubit) apuesta por hacer el flujo de datos completamente explícito. Los eventos son acciones que el usuario o el sistema disparan; los estados son las posibles situaciones del widget; el bloc mapea unos a otros.

// Eventos
sealed class CounterEvent {}
class IncrementPressed extends CounterEvent {}
class DecrementPressed extends CounterEvent {}

// Estado (puede ser más complejo)
class CounterState {
  final int count;
  const CounterState(this.count);
}

// Bloc
class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(const CounterState(0)) {
    on<IncrementPressed>((event, emit) {
      emit(CounterState(state.count + 1));
    });
    on<DecrementPressed>((event, emit) {
      emit(CounterState(state.count - 1));
    });
  }
}

// En el widget
class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => CounterBloc(),
      child: BlocBuilder<CounterBloc, CounterState>(
        builder: (context, state) {
          return Column(
            children: [
              Text('${state.count}'),
              ElevatedButton(
                onPressed: () =>
                    context.read<CounterBloc>().add(IncrementPressed()),
                child: const Text('+'),
              ),
            ],
          );
        },
      ),
    );
  }
}

La versión simplificada es Cubit: sin eventos, solo métodos que emiten estados directamente. Para casos donde el flujo es simple, un Cubit es mucho menos código que un Bloc completo.

Riverpod vs Bloc: cuándo usar cada uno

No hay una respuesta única, pero sí hay patrones claros:

Elige Riverpod cuando: tienes estado que se comparte entre partes distantes de la app, necesitas combinar múltiples fuentes de datos, o tu equipo prefiere un enfoque más flexible y menos ceremonioso. Riverpod brilla en apps donde el estado evoluciona de forma reactiva y las dependencias entre providers son complejas.

Elige Bloc cuando: el equipo quiere que cada cambio de estado sea trazable y auditable (hay herramientas de debug excelentes en Bloc), o trabajas en un proyecto grande donde la separación explícita entre eventos y estados facilita el trabajo en paralelo de varios desarrolladores. Bloc tiene una curva de aprendizaje mayor pero deja el flujo de datos muy claro.

En proyectos donde ya existe código con Provider (el predecesor de Riverpod), migrar a Riverpod es bastante gradual: los dos pueden coexistir. Si vienes de Bloc, no hay razón de peso para cambiar si el equipo ya lo conoce bien.

Cubit: el punto intermedio

Cubit merece mención aparte porque es la solución intermedia que mucha gente busca: más estructura que setState, menos boilerplate que Bloc completo. Si tu estado no necesita distinguir entre múltiples tipos de eventos (solo métodos que cambian el estado), un Cubit es suficiente y el código queda limpio.

class CounterCubit extends Cubit<int> {
  CounterCubit() : super(0);
  void increment() => emit(state + 1);
  void decrement() => emit(state - 1);
}

Para ver cómo encajan estas soluciones con las sealed classes de Dart 3.x, que permiten modelar estados de forma exhaustiva, puedes consultar el artículo sobre Dart 3.6 y pattern matching.

Si llegas de Android con experiencia en ViewModel y StateFlow de Jetpack, el modelo mental de Riverpod con NotifierProvider es bastante parecido. Los detalles están en Jetpack Compose en 2026 si quieres comparar los enfoques.

Imagen: Pexels / ready made

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP