In this article I will talk less about the Flutter State Management Solutions, but the art of managing the State in an App is the Source of most Bugs. This may be influenced, but not entirely removed from the State Library in use to isolate business logic from the platform and the external libraries.
State Holders
Here are the common places to keep state, categorized by scope:
1. Local (Widget/Screen-Oriented State)
Used when the state only matters within a single widget or screen.
Common Methods:
StatefulWidgetwithsetState()InheritedWidget/InheritedModelProviderwith.watch()or.read()scoped to a subtreeflutter_hooksfor concise state management
Whether you choose GetX, or BLoC, it is common to have a State – representing the information presented at one moment on the screen.
Examples:
- Form inputs
- Button toggle state – active, disabled, loading – the common states.
- Tab selection
- Text field content
- Page scroll position
2. Shared (App-Wide / Global State)
Used when multiple screens need to read/update the same state.
Common Tools:
Provider(withChangeNotifierorRiverpod)Riverpod(preferred for scalability and testability)BlocorCubitRedux(less common nowadays)GetX,MobX,Flutter Commands, etc.InheritedWidgetmanually (advanced use)
Examples:
- User login status
- Selected theme or locale
- Items in a shopping cart
- Auth token, user profile
- Real-time notifications
3. Event Bus (Pub-Sub or Observer Pattern)
Used when loose coupling is needed between components that don’t directly reference each other. It is also called Coordinator Pattern: https://pub.dev/packages/rx_bloc
Common Libraries:
event_buspackage- Streams (
StreamController.broadcast) Bloc‘s event system- WebSocket or MQTT events
Examples:
- Global events like “user logged out”
- Background sync updates
- Notifications or analytics
4. External State (Persistent / Remote / Outside Flutter)
State stored outside the app runtime, persisted across sessions or fetched live.
Examples:
- Local file system (JSON, XML, binary, etc.)
path_provider,dart:io,hive,shared_preferences
- Database
sqflite,drift,moor,objectbox
- Remote server
- REST API, GraphQL
- Cloud state
- Firebase Firestore, Realtime DB
- Secure storage
flutter_secure_storagefor tokens, PINs, secrets
Summary Table
| Scope | Lifetime | Access Level | Examples |
|---|---|---|---|
| Widget Local | Current session | One widget/screen | Input fields, animations |
| Global/App | Session-wide | Whole app | Theme, user profile, cart |
| Event Bus | On-demand triggers | Subscribed listeners | Logout, sync, notifications |
| External | Persistent | Any device/user | DB records, API data, cache |
Commonly Used Stateful Widgets in Flutter
Here are some frequently used stateful widgets provided by Flutter:
| Widget | Description |
|---|---|
| TextField | Allows user text input. Maintains its own state for text, focus, etc. |
| Checkbox | Lets the user select or deselect a boolean value. |
| Radio | A radio button that is part of a group. Maintains selected state. |
| Switch | An on/off toggle switch. |
| Slider | Allows the user to select a value from a range. |
| Form | A container for grouping multiple form fields (TextFormField, etc.). Maintains validation state. |
| PageView | A swipeable view that tracks and manages page index. |
| TabBarView | Associated with a TabController, which tracks the selected tab. |
| AnimatedContainer | Automatically transitions between old and new values when its properties change. |
| ExpansionTile | Expands/collapses its children dynamically. |
| Dismissible | Manages animation and swipe state of a list item. |
More Stateful Widgets in Flutter
In addition to the most common ones, here’s a broader list of Flutter stateful widgets—some are higher-level UI components, others are part of animations, scroll behavior, or user interaction:
| Widget | Description |
|---|---|
| ListView.builder | Built dynamic lists that remember scroll position, especially with a controller. |
| GridView.builder | Similar to ListView, but for grid layouts. |
| ScrollController (used with Scrollables) | Tracks scroll position, enables scroll-to behavior, etc. |
| InteractiveViewer | Allows pan, zoom, and rotate gestures with state tracking. |
| ReorderableListView | Allows drag-and-drop reordering of items. |
| TextFormField | Like TextField, but with form validation support. |
| BottomNavigationBar | Tracks current index of the selected tab. |
| Stepper | A step-based form or process navigator that tracks step state. |
| DropdownButton | Dropdown selection menu with changing selected item. |
| FutureBuilder | Reacts to async data and updates when the Future completes. |
| StreamBuilder | Reacts to incoming stream data. |
| Draggable / DragTarget | Supports drag-and-drop UI interactions. |
| AnimatedList | A list that animates when items are added/removed. |
| AnimatedSwitcher | Animates a widget change inside it with fade/slide/etc. |
| AnimatedBuilder | Rebuilds when an animation changes, often used with AnimationController. |
BottomSheet (when shown with showModalBottomSheet) |
Internally stateful to manage gestures, visibility. |
| Scaffold | Manages snackbar, drawers, and sometimes tabs—contains internal state. |
| InkWell | Tracks taps and gestures, may show ripple animation (uses internal state). |
Custom Stateful Widgets
When you define your own widget and need it to change over time, you use:
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
int counter = 0;
void increment() {
setState(() {
counter++;
});
}
@override
Widget build(BuildContext context) {
return Text('Counter: $counter');
}
}
StatefulWidget: Immutable wrapper (e.g.MyWidget)State: Mutable logic and UI, lives in_MyWidgetState
Summary
The business requirements you have force you to unite in one all the above
- screen/widget/local state,
- global state,
- external data sources
- external services
- user interactions
- platform integrations
- UI components with their – is focused, selected, loading/disabled state
Elevating it all into your State Managing Programming Units is what is needed – for less bugs. It is not always easy to do – when business requirements are changing. And when you forget to subscribe to changes on some of these state modifiers – is when bugs are most alive.
