← Flutter & Dart

Flutter State

What state management options do you know in Flutter, and when would you use each one?

Show answer — try answering out loud first

The most common are: setState for local state, Provider for simple shared state, Riverpod as an evolution of Provider that's safer and more testable, and Bloc/Cubit for business logic based on well-defined events and states. There's no single "best" one: it depends on the size of the project and the complexity of the state.

"State" is any data that changes and affects the UI. The key question is who needs that data:

  • setState: a widget's local state (a counter, a toggle). Simple and enough for what lives in a single widget.
  • Provider: exposes objects to a subtree and notifies changes (uses ChangeNotifier + notifyListeners()). Good for shared state in small and medium apps. Built on top of InheritedWidget.
  • Riverpod: similar to Provider but without depending on BuildContext to read, with better type safety and easier to test. Very popular in new projects.
  • Bloc / Cubit: separates the logic from the UI through events that come in and states that go out. Scales well in large apps and teams, at the cost of more code.

Rule: start simple (setState) and level up only when shared state and complexity justify it.

import 'package:flutter/material.dart';

// ChangeNotifier pattern (the foundation of Provider)
class CounterModel extends ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners(); // notifies the widgets that are listening
  }
}

// In a real project, with the provider package, you'd use it like this:
//
// ChangeNotifierProvider(
//   create: (_) => CounterModel(),
//   child: MyApp(),
// );
//
// And inside a widget:
//   final model = context.watch<CounterModel>();
//   Text('${model.count}');
//   onPressed: () => context.read<CounterModel>().increment();

Cramming the whole app into a single giant global state or, the other way around, using a heavy library like Bloc for a two-screen app. It's also common to call notifyListeners() too much (rebuilding the UI unnecessarily) or to modify the state without notifying (the UI doesn't react). Choose the tool based on the real complexity, not on hype.

"For local state I use setState. When state is shared across several widgets, I use Provider, which relies on ChangeNotifier and notifyListeners and under the hood uses InheritedWidget. In new projects I like Riverpod because it's more type-safe, doesn't depend on context to read, and is easier to test. And for large apps with complex business logic, Bloc or Cubit, which separate the UI from the logic with events and states. My rule of thumb is to start simple and level up only when the complexity calls for it."

Quick challenge

In the ChangeNotifier pattern, what happens if you change a value but forget to call notifyListeners()?

See answer

The value changes in memory, but the widgets that are listening don't find out and don't rebuild: the UI stays showing the old data. notifyListeners() is what triggers the update for all the listeners. It's conceptually the equivalent of forgetting setState.