What is State Management in Flutter?
State management is the process of storing, changing, sharing, and updating the data that controls what appears on the screen of a Flutter application. In simple terms, state is the data that the UI needs in order to display the correct screen at any moment.
Flutter follows a declarative UI approach. Instead of directly modifying an existing UI element, you change the application's state and Flutter rebuilds the appropriate part of the UI to reflect that state.
1. What is State?
State is any data that can change while an application is running and can affect the UI.
For example, consider a counter application:
int counter = 0;
When the user presses a button, the value changes:
counter = counter + 1;
The displayed number is dependent on this value, so counter is a piece of state.
Examples of State
- Counter value
- Selected checkbox
- Selected tab
- Text entered into a form
- Login status
- Shopping cart items
- Selected theme
- List of products
- Loading status
- Error messages
- API response data
- User preferences
2. Simple Definition
You can remember state management with this simple formula:
State changes
↓
UI needs to reflect the change
↓
Flutter rebuilds the required UI
↓
User sees updated information
3. Why Do We Need State Management?
Small applications can manage state locally inside widgets. As an application becomes larger, state may need to be shared between multiple widgets and screens.
Without an appropriate state-management approach, an application can become difficult to maintain because the same data may be passed through many widgets or duplicated in multiple places.
Example Without Proper State Management
HomeScreen
↓
Dashboard
↓
ProductScreen
↓
CartScreen
↓
CheckoutScreen
If every screen needs access to the same shopping cart, manually passing the cart through every widget can become cumbersome.
With Shared State
Cart State
/ | \
↓ ↓ ↓
Product Cart Checkout
Screen Screen Screen
A shared state-management approach can make application state easier to access and update.
4. StatelessWidget vs StatefulWidget
Understanding the difference between StatelessWidget and StatefulWidget is an important foundation for learning state management.
| StatelessWidget |
StatefulWidget |
| Does not maintain mutable state in a State object |
Works with a separate State object that can hold mutable state |
| Useful when UI depends only on input values |
Useful when UI changes during the widget's lifetime |
No setState() |
Can use setState() to trigger a rebuild |
| Examples: static labels and icons |
Examples: counters, toggles, interactive forms |
5. Basic StatefulWidget Example
import 'package:flutter/material.dart';
class CounterScreen extends StatefulWidget {
const CounterScreen({super.key});
@override
State createState() => _CounterScreenState();
}
class _CounterScreenState extends State {
int counter = 0;
void incrementCounter() {
setState(() {
counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Counter'),
),
body: Center(
child: Text(
'$counter',
style: const TextStyle(fontSize: 40),
),
),
floatingActionButton: FloatingActionButton(
onPressed: incrementCounter,
child: const Icon(Icons.add),
),
);
}
}
6. What is setState()?
setState() is a built-in Flutter mechanism used by a State object to tell Flutter that its internal state has changed and that the widget should be rebuilt.
setState(() {
counter++;
});
When the state changes without calling setState(), Flutter may not rebuild the widget to reflect the changed value.
7. What Happens When setState() is Called?
User taps button
↓
Event handler runs
↓
State changes
↓
setState() is called
↓
Flutter schedules rebuild
↓
build() runs again
↓
Updated UI appears
8. What Happens Without setState()?
Consider:
void incrementCounter() {
counter++;
}
The variable may change internally, but Flutter has not been explicitly notified. Therefore, the UI may continue showing the old value.
Correct approach:
void incrementCounter() {
setState(() {
counter++;
});
}
9. Types of State
Flutter documentation commonly discusses two conceptual types of state: ephemeral state and application state.
9.1 Ephemeral State
Ephemeral state, also called local or UI state, is state that can be contained neatly inside a single widget.
Examples:
- Current selected tab
- Whether a password is visible
- Animation progress
- Current checkbox selection
- Temporary form input
class Example extends StatefulWidget {
const Example({super.key});
@override
State createState() => _ExampleState();
}
class _ExampleState extends State {
bool isVisible = false;
@override
Widget build(BuildContext context) {
return Switch(
value: isVisible,
onChanged: (value) {
setState(() {
isVisible = value;
});
},
);
}
}
9.2 Application State
Application state, also called shared state, is state that needs to be accessed by multiple parts of the application or may need to persist across parts of the user experience.
Examples:
- Logged-in user information
- Shopping cart
- User preferences
- Notifications
- Application settings
- Shared API data
10. Local State vs Shared State
| Local State |
Shared State |
| Used by one widget or a small UI section |
Used by multiple widgets or screens |
| Usually simple to manage |
May require a dedicated state-management approach |
| Example: selected tab |
Example: logged-in user |
Often uses setState() |
Can use ChangeNotifier, Provider, or other approaches |
11. Parent-Managed State
Sometimes the state of a child widget is better managed by its parent. The parent stores the state and passes the current value and callback to the child.
class ParentWidget extends StatefulWidget {
const ParentWidget({super.key});
@override
State createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State {
bool isSelected = false;
@override
Widget build(BuildContext context) {
return ChildWidget(
isSelected: isSelected,
onChanged: (value) {
setState(() {
isSelected = value;
});
},
);
}
}
class ChildWidget extends StatelessWidget {
final bool isSelected;
final ValueChanged onChanged;
const ChildWidget({
super.key,
required this.isSelected,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
return Checkbox(
value: isSelected,
onChanged: (value) {
onChanged(value ?? false);
},
);
}
}
This pattern keeps the source of truth in the parent while the child displays and modifies the value through a callback.
12. State Flow in Flutter
State
↓
Widget Build
↓
UI
↓
User Interaction
↓
State Modification
↓
State
↓
UI Rebuilds
13. Declarative UI and State
Flutter uses a declarative approach to UI development:
UI = f(State)
This means the UI can be thought of as a function of the current state.
bool isLoggedIn = true;
if (isLoggedIn) {
return const HomeScreen();
} else {
return const LoginScreen();
}
If isLoggedIn changes, the UI reflects the new state automatically.
14. Why State Management Becomes Important in Large Apps
As applications grow, several widgets may need access to the same information.
Product Screen
↓
Add Product
↓
Shopping Cart State
↓
Cart Screen
↓
Checkout Screen
↓
Order Screen
If the cart state is needed by many screens, manually passing it through multiple widget constructors can become cumbersome.
15. Common State Management Approaches in Flutter
Built-in Approaches
setState()
ValueNotifier
InheritedNotifier
InheritedWidget
InheritedModel
ChangeNotifier
Community Packages and Architectures
- Provider
- Riverpod
- Bloc / Cubit
- Redux
- GetX
- Other community solutions
16. setState()
setState() is the simplest built-in approach for local, widget-specific state.
class Counter extends StatefulWidget {
const Counter({super.key});
@override
State createState() => _CounterState();
}
class _CounterState extends State {
int count = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('$count'),
ElevatedButton(
onPressed: () {
setState(() {
count++;
});
},
child: const Text('Increment'),
),
],
);
}
}
17. ValueNotifier
ValueNotifier is a Flutter-provided mechanism for holding a value and notifying listeners when it changes.
final counter = ValueNotifier(0);
counter.value++;
counter.value = 10;
A widget can listen to the value using ValueListenableBuilder:
ValueListenableBuilder(
valueListenable: counter,
builder: (context, value, child) {
return Text(
'$value',
style: const TextStyle(fontSize: 30),
);
},
)
18. InheritedWidget
InheritedWidget is a lower-level Flutter mechanism for making data available to descendant widgets in the widget tree.
class AppData extends InheritedWidget {
final String username;
const AppData({
super.key,
required this.username,
required super.child,
});
static AppData of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType()!;
}
@override
bool updateShouldNotify(AppData oldWidget) {
return username != oldWidget.username;
}
}
19. ChangeNotifier
ChangeNotifier is a Flutter SDK class that allows an object to notify listeners when its data changes.
class CounterModel extends ChangeNotifier {
int count = 0;
void increment() {
count++;
notifyListeners();
}
}
Calling notifyListeners() tells listening widgets that the state has changed and they should rebuild.
20. Provider
Provider is a community package that builds on Flutter's widget and notification mechanisms to make shared state easier to expose and consume.
flutter pub add provider
A common Provider setup uses ChangeNotifier, ChangeNotifierProvider, and Consumer.
Provider Example
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class CounterProvider extends ChangeNotifier {
int count = 0;
void increment() {
count++;
notifyListeners();
}
}
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => CounterProvider(),
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Consumer(
builder: (context, counter, child) {
return Text(
'${counter.count}',
style: const TextStyle(fontSize: 30),
);
},
),
),
floatingActionButton: Builder(
builder: (context) {
return FloatingActionButton(
onPressed: () {
context.read().increment();
},
child: const Icon(Icons.add),
);
},
),
),
);
}
}
21. Riverpod
Riverpod is another community state-management solution. It provides mechanisms for declaring and consuming application state and is commonly used when applications require more structured dependency and state management.
The exact APIs depend on the Riverpod version and package configuration, so developers should follow the current package documentation when implementing it.
22. Bloc and Cubit
Bloc and Cubit are popular approaches for organizing application state around predictable state changes.
UI
↓
Cubit / Bloc
↓
Repository
↓
API / Firebase / Database
This approach is useful for applications where business logic and state transitions need to be clearly separated from the UI.
23. State Management with API Calls
State management becomes especially useful when an application communicates with an API. An API request commonly has several states:
Initial → Loading → Success → Data Available
Or:
Initial → Loading → Error → Error Message
Example State Variables
bool isLoading = false;
String? errorMessage;
List products = [];
24. Loading, Success, Error and Empty States
| State |
Example UI |
| Initial |
Welcome or initial screen |
| Loading |
Progress indicator |
| Success |
Display retrieved data |
| Empty |
No data available message |
| Error |
Error message and retry option |
25. Example ViewModel with ChangeNotifier
class ProductViewModel extends ChangeNotifier {
bool isLoading = false;
String? error;
List products = [];
Future loadProducts() async {
isLoading = true;
error = null;
notifyListeners();
try {
await Future.delayed(const Duration(seconds: 2));
products = ['Laptop', 'Phone', 'Tablet'];
} catch (e) {
error = 'Unable to load products';
} finally {
isLoading = false;
notifyListeners();
}
}
}
26. State Management with Firebase
State management is particularly useful when working with Firebase because Firebase operations are asynchronous and application data can change over time.
Firebase Authentication → Authentication State → UI
Cloud Firestore → Task/Product Data → State Management → Flutter UI
27. Authentication State Example
StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
}
if (snapshot.hasData) {
return const HomeScreen();
}
return const LoginScreen();
},
)
28. State Management and Widget Rebuilds
Old State
↓
State Change
↓
Notification / setState
↓
Widget Rebuild
↓
New UI
29. State Management and Separation of Responsibilities
A scalable Flutter application should avoid placing all application logic inside widgets.
UI Layer
↓
ViewModel / State Manager
↓
Repository / Service
↓
API / Firebase / Database
30. Example Application Architecture
lib/
├── main.dart
├── models/
│ └── product.dart
├── views/
│ ├── home_screen.dart
│ └── product_screen.dart
├── viewmodels/
│ └── product_viewmodel.dart
├── services/
│ └── api_service.dart
└── repositories/
└── product_repository.dart
31. State Management Best Practices
- Use
setState() for simple local state
- Do not introduce a complex state-management package for every small widget
- Move shared application state into an appropriate shared-state solution
- Keep business logic outside large UI widgets
- Keep API and Firebase operations in services or repositories
- Represent loading, success, error, and empty states clearly
- Avoid unnecessary rebuilds
- Keep one clear source of truth for important shared data
- Dispose controllers, streams, and other resources when required
- Choose an approach that matches the project's complexity and team needs
32. Common State Management Mistakes
- Using
setState() for complicated global application state
- Creating duplicate copies of the same application data
- Putting API logic directly inside large widgets
- Forgetting to notify listeners when using
ChangeNotifier
- Calling
setState() after a widget has been disposed
- Not handling loading and error states
- Rebuilding a large widget tree unnecessarily
- Using a package without understanding the underlying state flow
- Keeping unrelated state in one large state-management class
33. How to Choose a State Management Approach?
| Requirement |
Possible Approach |
| Small widget-specific state |
setState() |
| Simple observable value |
ValueNotifier |
| Shared state with simple architecture |
Provider / ChangeNotifier |
| More structured application state |
Riverpod, Bloc/Cubit, or another suitable approach |
| Very simple application |
Built-in Flutter state mechanisms |
34. Practical Example: Shopping Cart State
class CartState extends ChangeNotifier {
final List items = [];
void addItem(String item) {
items.add(item);
notifyListeners();
}
void removeItem(String item) {
items.remove(item);
notifyListeners();
}
int get itemCount => items.length;
}
35. Practical Example: Theme State
class ThemeState extends ChangeNotifier {
bool isDarkMode = false;
void toggleTheme() {
isDarkMode = !isDarkMode;
notifyListeners();
}
}
36. Practical Example: Login State
class AuthState extends ChangeNotifier {
bool isLoggedIn = false;
void login() {
isLoggedIn = true;
notifyListeners();
}
void logout() {
isLoggedIn = false;
notifyListeners();
}
}
37. State Management Lifecycle
1. Create State
↓
2. Display State
↓
3. User / System Event
↓
4. Modify State
↓
5. Notify Flutter / Listeners
↓
6. Rebuild UI
↓
7. Display Updated State
38. State Management in a Real Application
Consider a food delivery application:
Authentication State → Home Screen → Restaurant State
↓
Cart State → Order State → Payment State
Each state has its own responsibility rather than placing all application data into a single massive state object.
39. State Management and Performance
Good state management helps control which parts of the UI need to rebuild when data changes.
Product List → unchanged → no rebuild
Cart Count → changed → rebuild only required UI
40. State Management and Asynchronous Data
Applications frequently receive data asynchronously from REST APIs, Firebase, Cloud Firestore, local databases, streams, and WebSockets.
Request Started → Loading = true
↓
Request Completed → Success / Error
↓
Loading = false → UI Updates
41. State Management with StreamBuilder
StreamBuilder(
stream: counterStream,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const CircularProgressIndicator();
}
return Text('${snapshot.data}');
},
)
42. State Management vs UI Management
| State Management |
UI Management |
| Stores application data |
Displays application data |
| Changes data based on events |
Builds widgets based on current data |
| Handles business/application state |
Handles presentation |
| Can communicate with repositories and services |
Uses widgets to render information |
43. Key Concepts to Remember
- State — Data that can change and affect the UI
- State Management — The process of organizing and updating that data
- setState — Built-in mechanism for notifying Flutter about local state changes
- StatefulWidget — A widget that works with a mutable State object
- Ephemeral State — Local state usually contained within a widget
- App State — Shared state used across multiple parts of an application
- ChangeNotifier — A Flutter class that can notify listeners when data changes
- Provider — A community package that simplifies exposing and consuming shared state
- ViewModel — A layer that holds presentation state and related logic
44. Interview Questions
Q1. What is state management in Flutter? State management is the process of managing data that can change during the lifetime of an application and ensuring that the UI reflects those changes.
Q2. What is state? State is data required to build the correct UI at a particular moment and can change over time.
Q3. What is setState()? setState() is a method used inside a State object to notify Flutter that its state has changed and the widget should be rebuilt.
Q4. What is the difference between StatelessWidget and StatefulWidget? A StatelessWidget does not maintain mutable state through a State object, while a StatefulWidget works with a separate State object that can hold mutable state and trigger rebuilds.
Q5. What is ephemeral state? Ephemeral state is local state that can usually be contained within a single widget, such as a selected tab or temporary UI value.
Q6. What is application state? Application state is shared state that may be needed by multiple parts of an application, such as authentication information, shopping cart data, or user preferences.
Q7. What is ChangeNotifier? ChangeNotifier is a Flutter SDK class that allows an object to notify listeners when its state changes.
Q8. What does notifyListeners() do? It notifies listeners that the data has changed so that listening widgets can rebuild and display the new state.
Q9. When should setState() be used? It is particularly suitable for simple, local, widget-specific or ephemeral state.
Q10. Why is state management important in large applications? Large applications often have shared data used by multiple widgets and screens. State management helps organize that data and its updates so the application remains easier to maintain.
45. Quick Revision
| Concept |
Key Point |
| State |
Data that can change and affect the UI |
| State Management |
Organizing and updating changing data |
setState() |
Notify Flutter about local state changes |
ChangeNotifier |
Notify listeners when state changes |
| Provider |
A community solution for sharing and consuming state |
| App State |
Shared application data |
| Ephemeral State |
Local widget-specific data |
46. Learning Outcome
After completing this topic, you should understand:
- What state means in Flutter
- Why state management is required
- How
StatefulWidget and setState() work
- The difference between ephemeral and application state
- How state can be shared between widgets
- How approaches such as
ChangeNotifier, Provider, and other state-management solutions can be used as applications become more complex
47. Summary
State management is one of the most important concepts in Flutter development. State represents data that can change and affect the user interface. For simple local state, Flutter's StatefulWidget and setState() are often sufficient. As an application grows, shared-state mechanisms and state-management packages can help organize data and application logic.
The main idea to remember is:
Change State
↓
Notify / Trigger Rebuild
↓
Flutter Rebuilds UI
↓
User Sees Current State
Flutter provides several built-in state-management mechanisms and supports many community solutions. The appropriate approach depends on the size, architecture, requirements, and team preferences of the application.