State Management in Flutter: setState, Provider, Riverpod or Bloc?

Choosing a state management approach is one of the first questions every Flutter developer faces. This article explains setState, Provider, Riverpod and Bloc with code and tells you which to choose for which project.

3 min read2 views
State Management in Flutter: setState, Provider, Riverpod or Bloc?

"State" is the data your UI is built from and that changes over time: a shopping-cart counter, the signed-in user, a search result. The core question is: where does that data live, and how does the UI update when it changes?

#1. setState, the simplest option

Enough for the local state of one widget (such as whether a panel is open):

class CounterPage extends StatefulWidget {
  const CounterPage({super.key});
  @override
  State<CounterPage> createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () => setState(() => count++),
      child: Text('$count'),
    );
  }
}

Trouble starts when several screens need the same data. Lift state up as far as you can, but past a point it gets complicated without proper tooling.

#2. Provider

A thin layer over InheritedWidget. You create a ChangeNotifier and place it above your widgets in the tree:

class CartModel extends ChangeNotifier {
  final List<String> items = [];
  void add(String item) {
    items.add(item);
    notifyListeners();
  }
}

// near the root
ChangeNotifierProvider(create: (_) => CartModel(), child: const MyApp());

// in any widget below
final cart = context.watch<CartModel>();

Easy to learn and works great for small to medium projects.

#3. Riverpod

The evolution of Provider: independent of BuildContext, easier to test and safer at compile time:

final counterProvider = NotifierProvider<Counter, int>(Counter.new);

class Counter extends Notifier<int> {
  @override
  int build() => 0;
  void increment() => state++;
}

// in a ConsumerWidget
final count = ref.watch(counterProvider);
ref.read(counterProvider.notifier).increment();

It also ships tools for asynchronous data such as network requests.

#4. Bloc

An "event to state" pattern. Every action is an event and the output is a new state. It needs more code, but its structure and predictability pay off for large teams and complex apps.

#Which should I pick?

  • Small project or learning: setState and ValueNotifier.
  • Medium, solo project: Provider or Riverpod.
  • Large team project with complex flows: Riverpod or Bloc.

#Three practical tips

  • Keep state small and close to where it is used.
  • Separate business logic from widgets so it can be tested on its own.
  • Pick one tool and stay consistent; mixing several is worse than one mediocre choice.

The best state management tool is the one your team knows well.

Learn Flutter the professional, project-based way

The FastFlutter VIP course takes you from zero to a published app, with direct support from the instructor.

View the VIP course