Flutter State Management 101
Compare setState, Provider, and Riverpod with practical guidance on when to use each.
What You'll Learn
- Know when to use setState vs a state management library
- Wire up Provider for app-wide shared state
- Migrate to Riverpod for type-safe, testable state
- Avoid common pitfalls like rebuilding too many widgets
- Structure a Flutter app around clean state boundaries
Prerequisites
- Basic Dart syntax
- Familiarity with Flutter widgets and StatefulWidget
Step-by-Step Guide
Step 1
1. setState — Widget-Local State
setState is Flutter's built-in way to update a StatefulWidget. It triggers a rebuild of that widget's subtree. Use it only for state that is truly local to a single widget (e.g., whether a dropdown is open, a tab index, a text field value). If two widgets need the same state, lift it up or reach for a proper state solution.
Step 2
2. InheritedWidget — The Foundation
Provider and Riverpod both build on InheritedWidget, Flutter's mechanism for passing data down the tree without manually threading it through constructors. Understanding InheritedWidget helps you debug why context.watch() triggers rebuilds. You rarely write InheritedWidget directly, but knowing it exists explains everything.
Step 3
3. Provider — Simple App-Wide State
Wrap your MaterialApp with MultiProvider. Create a ChangeNotifier class for each domain of state. Use context.watch<T>() to rebuild on changes, context.read<T>() to call methods without subscribing. Provider is great for small to medium apps. Add provider: ^6.x to pubspec.yaml.
Step 4
4. Riverpod — Type-Safe & Testable
Riverpod removes the BuildContext dependency for reading providers, making it fully testable in plain Dart. Define providers as top-level constants. Use ConsumerWidget instead of StatelessWidget. ref.watch() subscribes, ref.read() doesn't. StateNotifierProvider + StateNotifier replaces ChangeNotifier for more explicit state transitions.
Step 5
5. Choosing the Right Tool
Single screen with isolated state → setState. Small app with a few shared values → Provider. Large app, teams, or needing testability → Riverpod. Avoid over-engineering: start simple and refactor when friction appears. Don't introduce BLoC or Redux unless the team is already familiar and the complexity justifies it.
Further Reading & Resources
Ready to put this into practice?
Apply for a real App Development internship and ship production projects with mentorship.
