// back_to_blog Mobile Development

Flutter State Management: Provider, Bloc, and Riverpod

Jul 11, 2026 · 3 min read · 2 tags
Flutter State Management: Provider, Bloc, and Riverpod

Pick for the team and the problem size

State management arguments online are louder than most apps need. For small screens, local setState or a simple provider is fine. Complexity should match the product—not the Twitter discourse. Official overview: Flutter state management intro.

Provider — simple and fine to start

class Counter extends ChangeNotifier {
  int value = 0;
  void increment() {
    value++;
    notifyListeners();
  }
}

// wrap app
ChangeNotifierProvider(create: (_) => Counter(), child: MyApp());

// read
context.watch<Counter>().value;
context.read<Counter>().increment();

Good for learning and growing apps. Can get messy if everything becomes a global ChangeNotifier without structure.

Bloc — explicit events and states

sealed class CounterEvent {}
class IncrementPressed extends CounterEvent {}

class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0) {
    on<IncrementPressed>((event, emit) => emit(state + 1));
  }
}

I like Bloc when events/states need to be explicit—especially with multiple async streams and clearer separation between UI and business logic. More boilerplate, more predictability. Package: bloclibrary.dev.

Riverpod — my default for new non-trivial apps

final counterProvider = StateProvider<int>((ref) => 0);

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

Riverpod gives compile-time safety and testable providers without some InheritedWidget footguns. Learning curve is real; payoff is cleaner dependency wiring. Docs: riverpod.dev.

When to graduate

  1. Start with setState / Provider for small features.
  2. Move to Riverpod (or Bloc if the team already standardizes on it) when screens share async data and testing matters.
  3. Do not rewrite a working Provider app mid-launch because a blog said Riverpod is “modern.”

Testing note

Whatever you pick, keep business logic out of widgets when you can. Provider/Bloc/Riverpod all support overriding dependencies in tests—use that instead of hitting real APIs in widget tests.

Troubleshooting and common mistakes

Most failures I see are configuration and process issues, not “the framework is broken.” Slow down: reproduce on a clean environment, read the exact error, and change one variable at a time.

  • Confirm you are on the documented major version of the tool you are following.
  • Prefer official docs over random outdated blog snippets when commands disagree.
  • Keep lockfiles committed so teammates and CI install the same dependency graph.
  • Separate “works on my machine” fixes (PATH, SDK licenses, local services) from application bugs.

What to do next

Implement the smallest vertical slice from this article on a throwaway branch, then promote the patterns into your real app. Guides that stay theoretical never catch the auth, env, and deploy footguns that actually burn time.

Side-by-side decision notes

  • Provider: fastest to teach; watch for god-notifiers
  • Bloc: best when event sourcing-ish clarity helps the team
  • Riverpod: best default for new apps that will grow and need tests

I avoid mixing all three in one app. Pick a house style and stick to it unless you are migrating deliberately.

Async data pattern (any library)

Separate “loading / data / error” in the state model. UI should not guess. Whether that is a Bloc state class or an AsyncValue in Riverpod, the idea is the same.

Enjoyed this article?

Explore more posts or get in touch about a project.