← Back to list

Flutter State Management in 2026: setState vs Riverpod vs BLoC

Compare Flutter’s three main state management approaches with real code. setState for simple apps, Riverpod for modern projects, BLoC for…

Syed Umair Ahmed · 2026-04-13 02:46 · 200 claps · 4.3 min read
#flutter #state-management #flutter-riverpod #dart #software-development
Open on Medium ↗
Wiki topics: BIZ · Business Strategy 📱 · Mobile Development

Flutter State Management in 2026: setState vs Riverpod vs BLoC

Compare Flutter’s three main state management approaches with real code. setState for simple apps, Riverpod for modern projects, BLoC for enterprise. Updated 2026.

Every Flutter tutorial argues about state management. Here’s the truth: all three work. The question is which one fits your project’s size, team, and architecture needs.

I’ll show the same feature implemented three ways so you can compare the actual code, not just opinions.

🔍 Quick Decision

┌──────────────┬─────────────────────────────────┬─────────────┬─────────────┬────────────────┐
│ Approach     │ Best For                        │ Boilerplate │ Testability │ Learning Curve │
├──────────────┼─────────────────────────────────┼─────────────┼─────────────┼────────────────┤
│ setState     │ Prototypes, single-screen state │ None        │ Hard        │ Easiest        │
│ Riverpod 3.x │ Most projects                   │ Low         │ Excellent   │ Medium         │
│ BLoC 9.x     │ Enterprise, large teams         │ Medium      │ Excellent   │ Steeper        │
└──────────────┴─────────────────────────────────┴─────────────┴─────────────┴────────────────┘

1️⃣ setState — The Built-In Option

class CounterScreen extends StatefulWidget {
  const CounterScreen({super.key});
  @override
  State<CounterScreen> createState() => _CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
  int _count = 0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Text('Count: $_count')),
      floatingActionButton: FloatingActionButton(
        onPressed: () => setState(() => _count++),
        child: const Icon(Icons.add),
      ),
    );
  }
}

Pros: Zero setup, zero dependencies, works out of the box.

Cons: State lives inside the widget. Can’t share it across screens. Can’t test business logic separately. When the app grows, you end up with “God widgets” that do everything.

Use when: Prototyping, learning Flutter, state that belongs to a single screen (form input, animation controller, toggle).

2️⃣ Riverpod 3.x — Modern Default

flutter_riverpod: ^3.3.1  # or ^3.2.1 for stable channel

Important: Riverpod 3.x deprecated StateProvider, StateNotifier, and ChangeNotifier. Use only the modern six: Provider, FutureProvider, StreamProvider, NotifierProvider, AsyncNotifierProvider, StreamNotifierProvider.

Counter with Riverpod

import 'package:flutter_riverpod/flutter_riverpod.dart';
class CounterNotifier extends Notifier<int> {
  @override
  int build() => 0;
  void increment() => state++;
}
final counterProvider = NotifierProvider<CounterNotifier, int>(
  CounterNotifier.new,
);
// Widget
class CounterScreen extends ConsumerWidget {
  const CounterScreen({super.key});
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);
    return Scaffold(
      body: Center(child: Text('Count: $count')),
      floatingActionButton: FloatingActionButton(
        onPressed: () => ref.read(counterProvider.notifier).increment(),
        child: const Icon(Icons.add),
      ),
    );
  }
}

Async Example (API Call)

final usersProvider = FutureProvider<List<User>>((ref) async {
  final response = await Dio().get('https://api.myapp.com/users');
  return (response.data as List).map((json) => User.fromJson(json)).toList();
});
// Widget — handles loading, error, and data automatically
class UsersScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return switch (ref.watch(usersProvider)) {
      AsyncData(:final value) => ListView.builder(
        itemCount: value.length,
        itemBuilder: (_, i) => ListTile(title: Text(value[i].name)),
      ),
      AsyncError(:final error) => Center(child: Text('Error: $error')),
      _ => const Center(child: CircularProgressIndicator()),
    };
  }
}

Pros: Less boilerplate than BLoC, compile-time safety, built-in async handling, excellent testing.

Cons: Learning curve for the provider types. Code generation optional but adds complexity.

3️⃣ BLoC 9.x — Enterprise Standard

flutter_bloc: ^9.1.1
equatable: ^2.0.8
bloc_test: ^10.0.0  # dev dependency

Counter with BLoC

// Events — sealed class (Dart 3)
sealed class CounterEvent {}
class Increment extends CounterEvent {}
// States — Equatable for proper comparison
class CounterState extends Equatable {
  final int count;
  const CounterState(this.count);
  @override
  List<Object?> get props => [count];
}
// BLoC
class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(const CounterState(0)) {
    on<Increment>((event, emit) => emit(CounterState(state.count + 1)));
  }
}
// Widget
class CounterScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => CounterBloc(),
      child: BlocBuilder<CounterBloc, CounterState>(
        builder: (context, state) {
          return Scaffold(
            body: Center(child: Text('Count: ${state.count}')),
            floatingActionButton: FloatingActionButton(
              onPressed: () => context.read<CounterBloc>().add(Increment()),
              child: const Icon(Icons.add),
            ),
          );
        },
      ),
    );
  }
}

Pros: Explicit event-driven architecture, excellent tooling (bloc_test), event tracing for debugging, enforced patterns.

Cons: More boilerplate (events, states, BLoC classes). For a simple counter, BLoC is overkill.

📊 Side-by-Side Comparison

┌─────────────────────────────────┬──────────┬──────────────┬──────────┐
│ Feature                         │ setState │ Riverpod 3.x │ BLoC 9.x │
├─────────────────────────────────┼──────────┼──────────────┼──────────┤
│ Lines for counter               │ ~15      │ ~25          │ ~35      │
│ Lines for API + list            │ ~40      │ ~20          │ ~50      │
│ Testable without widget         │ ❌       │ ✅           │ ✅      │
│ Shares state across screens     │ ❌       │ ✅           │ ✅      │
│ Event tracing/debugging         │ ❌       │ ❌           │ ✅      │
│ Code generation optional        │ N/A      │ Yes          │ No       │
│ Flutter official recommendation │ N/A      │ N/A          │ N/A      │
└─────────────────────────────────┴──────────┴──────────────┴──────────┘

Note: Flutter’s official architecture docs at **docs.flutter.dev/app-architecture recommend MVVM** (ViewModel + Repository), not BLoC or Riverpod specifically. Both BLoC and Riverpod implement the ViewModel pattern effectively. The official docs are framework-agnostic.

⚠️ Common Mistakes

1. Using setState for shared state — The moment two screens need the same data, setState falls apart. Move to Riverpod or BLoC early.

2. Using BLoC for a 3-screen app — The boilerplate overhead isn’t worth it. Use Riverpod or even setState for simple apps.

3. Using Riverpod’s legacy APIsStateProvider, StateNotifier, ChangeNotifier are moved to legacy.dart in Riverpod 3.x. Use Notifier, AsyncNotifier, StreamNotifier.

4. Not using Equatable with BLoC — Without Equatable, BLoC uses reference equality. Two identical states look “different,” causing unnecessary UI rebuilds.

5. Not using sealed classes for BLoC events/statessealed (Dart 3) gives exhaustive pattern matching. The compiler catches missing state handlers at compile time.

❓ FAQ

Q: What does the Flutter team officially recommend?

Flutter’s docs recommend MVVM with two layers (UI + Data). Both Riverpod and BLoC implement MVVM patterns. The Flutter team doesn’t endorse a specific third-party state management package.

Q: Can I switch from BLoC to Riverpod mid-project?

Yes, but it’s a significant refactor. You’d replace BlocProviders with Riverpod providers, events/states with Notifiers, and BlocBuilders with ConsumerWidgets. Do it feature by feature, not all at once.

Q: What about GetX?

GetX is a single-maintainer project with maintenance concerns. Multiple sources flag it as a risk for production apps. Riverpod and BLoC are safer long-term choices with larger teams and corporate backing.

Q: Cubit or BLoC?

Cubit is simpler (methods instead of events). BLoC is stricter (explicit event classes). Start with Cubit. Switch to BLoC when you need event tracing or complex event transformations.

Conclusion

Start with setState when learning. Move to Riverpod when your app needs shared state across screens — it's the modern default with the least boilerplate. Choose BLoC when your team values strict architecture, event tracing, or you're building for enterprise.

All three approaches work in production. The right choice depends on your team and project, not on benchmarks or Twitter debates.

Package versions verified: flutter*riverpod: ^3.3.1, flutter*bloc: ^9.1.1, equatable: ^2.0.8, bloc_test: ^10.0.0.

Follow me for more Flutter + AI content 🚀


메타데이터
post_id
84bc40d7d9ce
slug
flutter-state-management-in-2026-setstate-vs-riverpod-vs-bloc-84bc40d7d9ce
url
https://medium.com/@umairsyedahmed282/flutter-state-management-in-2026-setstate-vs-riverpod-vs-bloc-84bc40d7d9ce
canonical_url
https://medium.com/@umairsyedahmed282/flutter-state-management-in-2026-setstate-vs-riverpod-vs-bloc-84bc40d7d9ce
author_url
https://medium.com/@umairsyedahmed282
status
ok
fetched_at
2026-08-26 18:57:11