What Is State in Flutter?
Understanding State
In Flutter, state is any data that can change over the lifetime of a widget. When state changes, the framework rebuilds the affected parts of the UI to reflect those changes. This is the core principle behind Flutter’s reactive programming model: UI = f(state). Your user interface is always a function of the current state.
Every time a piece of state changes, Flutter determines which widgets depend on that state and triggers a rebuild of those widgets. This means you never manually manipulate the DOM or view hierarchy — you simply update the state and let Flutter handle the rest.
Ephemeral State vs App State
Flutter documentation distinguishes between two broad categories of state:
Ephemeral State (Local State)
Ephemeral state is state that belongs to a single widget and does not need to be shared. It is short-lived, contained, and managed entirely within one StatefulWidget. Examples include:
- The current page index in a
PageView - Whether a checkbox is checked or not
- The current value of a text field
- The progress of an animation
- Whether a dropdown menu is open or closed
Ephemeral State Example
class ToggleButton extends StatefulWidget {
const ToggleButton({super.key});
@override
State<ToggleButton> createState() => _ToggleButtonState();
}
class _ToggleButtonState extends State<ToggleButton> {
// This is ephemeral state - only this widget cares about it
bool _isOn = false;
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
setState(() {
_isOn = !_isOn;
});
},
style: ElevatedButton.styleFrom(
backgroundColor: _isOn ? Colors.green : Colors.red,
),
child: Text(_isOn ? 'ON' : 'OFF'),
);
}
}
App State (Shared State)
App state is state that needs to be shared across multiple widgets or even across the entire application. It typically outlives individual widgets and affects many parts of the UI. Examples include:
- User authentication status (logged in or out)
- Shopping cart contents
- User preferences (theme, language, font size)
- Notification count
- Data fetched from an API
App State Conceptual Example
// These are examples of app state - shared across many widgets
class AppState {
final User? currentUser; // Auth status affects many screens
final List<CartItem> cartItems; // Cart shown in multiple places
final ThemeMode themeMode; // Theme affects entire app
final String locale; // Language affects all text
const AppState({
this.currentUser,
this.cartItems = const [],
this.themeMode = ThemeMode.system,
this.locale = 'en',
});
}
UI = f(state) Explained
The formula UI = f(state) means that the user interface is a pure function of the application’s state. Given the same state, the UI will always look the same. This is a fundamental shift from imperative UI programming where you manually update individual views.
Imperative vs Declarative
// IMPERATIVE (traditional approach - NOT how Flutter works)
// You manually update each UI element:
// nameLabel.setText("Edrees");
// avatarImage.setVisible(true);
// loginButton.setVisible(false);
// DECLARATIVE (Flutter approach)
// You describe what the UI should look like given the current state:
@override
Widget build(BuildContext context) {
return Column(
children: [
if (user != null) ...[
Text(user!.name), // Shown when logged in
CircleAvatar(
backgroundImage: NetworkImage(user!.avatarUrl),
),
] else ...[
const Text('Welcome, Guest'),
ElevatedButton(
onPressed: _login,
child: const Text('Log In'),
),
],
],
);
}
When State Changes Trigger Rebuilds
Flutter does not automatically detect variable changes. You must explicitly tell Flutter that state has changed by calling setState() inside a StatefulWidget. When you call setState(), the following happens:
- The closure you pass to
setState()executes, modifying your state variables - Flutter marks the widget as “dirty” (needing a rebuild)
- On the next frame, Flutter calls the
build()method again - Flutter compares the new widget tree with the previous one
- Only the changed parts of the UI are updated on screen
setState Triggers a Rebuild
class CounterWidget extends StatefulWidget {
const CounterWidget({super.key});
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _count = 0;
void _increment() {
// Without setState, the UI would NOT update
setState(() {
_count++;
});
// After setState, build() will be called again
}
@override
Widget build(BuildContext context) {
// This method runs every time setState is called
print('Building CounterWidget with count: \$_count');
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Count: \$_count',
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _increment,
child: const Text('Increment'),
),
],
);
}
}
setState(), the variable will update in memory but the UI will not reflect the change. This is one of the most common bugs for Flutter beginners.StatefulWidget vs StatelessWidget Recap
Understanding the difference between these two widget types is essential for managing state:
StatelessWidget
A StatelessWidget has no mutable state. Once built, it does not change unless its parent rebuilds it with different parameters. It is used for static content or content that depends entirely on its constructor arguments.
StatelessWidget Example
class UserCard extends StatelessWidget {
final String name;
final String email;
final String avatarUrl;
const UserCard({
super.key,
required this.name,
required this.email,
required this.avatarUrl,
});
@override
Widget build(BuildContext context) {
// This widget has no internal state
// It only displays what it receives via constructor
return Card(
child: ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(avatarUrl),
),
title: Text(name),
subtitle: Text(email),
),
);
}
}
StatefulWidget
A StatefulWidget maintains mutable state that can change during the widget’s lifetime. It consists of two classes: the widget class (immutable) and the state class (mutable).
Examples of Different State Types
Let us look at concrete examples of state you encounter in real Flutter applications:
Form Input State
Text fields, checkboxes, radio buttons, and sliders all maintain ephemeral state. The current text in a field or the selected option is local state managed by the widget.
Authentication State
Whether a user is logged in, their profile information, and their access token represent app state. Multiple screens need to know if the user is authenticated to show appropriate content.
Shopping Cart State
Items in a cart, quantities, and the total price are app state shared between the product listing, cart screen, and checkout flow.
Theme Preference State
Light mode, dark mode, or system-default is app state that affects every widget in the application. Changing the theme must trigger a rebuild of the entire widget tree.
Multiple State Types in One App
class ProductPage extends StatefulWidget {
final String productId;
const ProductPage({super.key, required this.productId});
@override
State<ProductPage> createState() => _ProductPageState();
}
class _ProductPageState extends State<ProductPage> {
// Ephemeral state - only this widget cares
int _selectedQuantity = 1;
bool _showDescription = false;
int _selectedImageIndex = 0;
// App state would come from a state management solution
// e.g., Provider, Riverpod, Bloc
// final cartProvider = ...
// final authProvider = ...
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Product Details')),
body: Column(
children: [
// Uses ephemeral state for image carousel
ImageCarousel(
selectedIndex: _selectedImageIndex,
onIndexChanged: (index) {
setState(() {
_selectedImageIndex = index;
});
},
),
// Uses ephemeral state for quantity
QuantitySelector(
quantity: _selectedQuantity,
onChanged: (qty) {
setState(() {
_selectedQuantity = qty;
});
},
),
// Toggle is ephemeral state
TextButton(
onPressed: () {
setState(() {
_showDescription = !_showDescription;
});
},
child: Text(
_showDescription ? 'Hide Details' : 'Show Details',
),
),
if (_showDescription)
const Text('Full product description here...'),
],
),
);
}
}
State Management Landscape Overview
As your app grows beyond simple ephemeral state, you will need more sophisticated tools. The Flutter ecosystem offers many state management solutions, each with different trade-offs:
| Approach | Complexity | Best For |
|---|---|---|
| setState | Low | Simple widgets with local state |
| InheritedWidget | Medium | Passing data down the widget tree |
| Provider | Medium | Most apps, recommended by Flutter team |
| Riverpod | Medium-High | Type-safe, testable, compile-safe |
| Bloc / Cubit | High | Large apps, strict architecture |
| GetX | Low | Rapid prototyping (controversial) |
| Redux | High | Predictable state, time-travel debugging |
setState for simple cases, learn InheritedWidget to understand how Flutter passes data down the tree, then adopt Provider or Riverpod as your app grows. In this tutorial series, we will cover each approach step by step.setState) to notify Flutter about those changes.