Ask five Flutter developers which state management library to use and you will hear five answers, often with strong feelings attached. The debate hides a simpler truth. The library matters less than where you put your logic and how data flows through the app. Still, the choice affects how you test, how new developers onboard, and how much boilerplate you write. Here is how the main options compare and how we choose between them on client projects.
What Problem Are We Solving?
Flutter rebuilds widgets from state. Some state belongs to one widget, such as whether a dropdown is open or the text in a field. Other state is shared across screens, such as the logged-in user, the cart, or a list loaded from an API. Ephemeral state is fine inside a StatefulWidget with setState. App state needs a home outside the widget tree so several screens can read it, it survives navigation, and business logic can be tested without building widgets.
The Main Options
| Option | How it works | Strengths | Trade-offs |
|---|---|---|---|
| setState | State lives in a StatefulWidget and the widget rebuilds itself | Built in, nothing to learn | Does not scale to shared state, and logic gets mixed into UI code |
| Provider with ChangeNotifier | A class holds state and calls notifyListeners, and widgets listen through the widget tree | Small API, used in the official Flutter documentation, easy for beginners | Depends on BuildContext, and runtime errors appear when a provider is missing |
| Riverpod | Providers are declared globally and read through a ref, with built-in support for async data | Compile-time safety, easy testing with overrides, good handling of loading and error states | More concepts to learn, and the API has changed across major versions |
| Bloc or Cubit | UI sends events or calls methods, and the bloc emits new immutable states | Very predictable, clear separation, strong tooling and testing library | More files and boilerplate, which can feel heavy for small apps |
Other libraries such as GetX and MobX exist and have their fans. For long-lived business apps we stay with the options above because they have large communities, clear documentation, and patterns that most Flutter developers you hire will already know.
How We Choose
- Small app or a team new to Flutter: Provider with ChangeNotifier. It follows the official app architecture guide and is easy to explain.
- Most new business apps with a lot of API data: Riverpod. AsyncValue makes loading, error, and data states explicit, and overrides make tests short.
- Large teams, regulated domains, or apps with complex flows such as payments: Bloc. The strict event to state model makes behaviour easy to trace and review.
- An existing codebase: keep what it uses unless it is causing real problems. Mixing three libraries in one app is worse than any single choice.
Structure Matters More Than the Library
- 1Put API calls and local storage in repository classes. Widgets and state classes never call http or a database directly.
- 2Keep business rules in the state layer, such as a Notifier, Cubit, or ChangeNotifier, so they can be unit tested without widgets.
- 3Make state objects immutable and create new copies on change. Packages such as freezed reduce the boilerplate.
- 4Model loading, success, empty, and error states explicitly so every screen handles all of them.
- 5Inject repositories so tests can replace them with fakes.
- 6Group code by feature, such as auth, orders, and profile, rather than by type, so a feature can be understood in one folder.
Performance Tips
- Listen only to the part of the state a widget needs, using select in Provider and Riverpod or buildWhen in Bloc, to avoid rebuilding whole screens.
- Push state consumers down the tree so a small widget rebuilds instead of its parent.
- Use const constructors wherever possible.
- Check rebuilds with the Flutter DevTools performance view in profile mode on a real device.
A clean repository layer and well-defined states make any of these libraries pleasant. Without them, none of them will save the project.
Our Flutter training builds the same small app with Provider, Riverpod, and Bloc side by side, so teams can see the differences in real code and agree on one standard before starting their own project.
Key takeaways
- Use setState for state that belongs to one widget, and a state management library for shared app state.
- Provider is the simplest start, Riverpod fits most API-heavy apps, and Bloc suits large teams and complex flows.
- Keep one approach per codebase instead of mixing libraries.
- Repositories, immutable state, and explicit loading and error states matter more than the library.
- Rebuild only what changes by selecting the exact state a widget needs.


