setState() State Management in Flutter
setState() is one of the simplest and most commonly used ways to manage local or widget-specific state in Flutter. It is provided by the State class and tells the Flutter framework that the internal state of a widget has changed and that the widget's UI should be rebuilt.
1. What is State in Flutter?
State is data that can change during the lifetime of a widget and can affect what appears on the screen.
Examples of state include:
- Counter value
- Whether a checkbox is selected
- Whether a password is visible
- Selected tab index
- Whether a button is loading
- Selected item in a dropdown
- Favorite/unfavorite status
- Current slider value
When this data changes, Flutter needs to rebuild the relevant UI so that the user can see the updated value.
2. What is setState()?
setState() is a method used inside a State class to notify Flutter that the state of a widget has changed.
The basic syntax is:
setState(() {
// Change the state here
});
When setState() is called, Flutter marks the associated widget as needing to be rebuilt and calls its build() method again during the next appropriate update.
3. Why Do We Need setState()?
Changing a variable by itself does not tell Flutter that the UI needs to change.
For example:
int count = 0;
void increment() {
count++;
}
The value of count changes in memory, but Flutter is not automatically informed that the UI should rebuild.
Instead, use:
int count = 0;
void increment() {
setState(() {
count++;
});
}
Now Flutter knows that the state changed and can rebuild the widget.
4. setState() and StatefulWidget
setState() is normally used with a StatefulWidget.
A StatefulWidget is implemented using two classes:
- A class extending
StatefulWidget
- A class extending
State
The mutable state is stored in the State class, while the widget configuration remains immutable.
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 Text('$count');
}
}
5. Basic setState() Example
The following example creates a counter application where pressing the button increases the counter.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: CounterScreen(),
);
}
}
class CounterScreen extends StatefulWidget {
const CounterScreen({super.key});
@override
State createState() => _CounterScreenState();
}
class _CounterScreenState extends State {
int count = 0;
void incrementCounter() {
setState(() {
count++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('setState Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'$count',
style: const TextStyle(fontSize: 40),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: incrementCounter,
child: const Text('Increment'),
),
],
),
),
);
}
}
How It Works
- The initial value of
count is 0.
- The user presses the Increment button.
incrementCounter() is called.
setState() changes count.
- Flutter schedules the widget for rebuilding.
- The
build() method runs again.
- The new counter value is displayed.
6. setState() Execution Flow
The general flow is:
User Action
↓
Event Handler
↓
setState()
↓
State Variable Changes
↓
Flutter Marks Widget for Rebuild
↓
build() Runs Again
↓
Updated UI
For example:
onPressed: () {
setState(() {
count++;
});
}
7. setState() with Boolean State
setState() is useful when a UI element has two or more states.
For example, showing and hiding a password:
class PasswordField extends StatefulWidget {
const PasswordField({super.key});
@override
State createState() => _PasswordFieldState();
}
class _PasswordFieldState extends State {
bool isObscured = true;
@override
Widget build(BuildContext context) {
return TextField(
obscureText: isObscured,
decoration: InputDecoration(
labelText: 'Password',
suffixIcon: IconButton(
icon: Icon(
isObscured
? Icons.visibility
: Icons.visibility_off,
),
onPressed: () {
setState(() {
isObscured = !isObscured;
});
},
),
),
);
}
}
Here, isObscured is local widget state. Calling setState() causes the password field to rebuild with the new value.
8. setState() with Checkbox
class TermsCheckbox extends StatefulWidget {
const TermsCheckbox({super.key});
@override
State createState() => _TermsCheckboxState();
}
class _TermsCheckboxState extends State {
bool accepted = false;
@override
Widget build(BuildContext context) {
return CheckboxListTile(
title: const Text('Accept Terms and Conditions'),
value: accepted,
onChanged: (value) {
setState(() {
accepted = value ?? false;
});
},
);
}
}
9. setState() with Switch
class ThemeSwitch extends StatefulWidget {
const ThemeSwitch({super.key});
@override
State createState() => _ThemeSwitchState();
}
class _ThemeSwitchState extends State {
bool isDark = false;
@override
Widget build(BuildContext context) {
return Switch(
value: isDark,
onChanged: (value) {
setState(() {
isDark = value;
});
},
);
}
}
10. setState() with a List
setState() can also be used when modifying a local list.
class ShoppingList extends StatefulWidget {
const ShoppingList({super.key});
@override
State createState() => _ShoppingListState();
}
class _ShoppingListState extends State {
final List items = [];
void addItem() {
setState(() {
items.add('New Item');
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(
onPressed: addItem,
child: const Text('Add Item'),
),
Expanded(
child: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index]),
);
},
),
),
],
);
}
}
11. setState() with a List: Add and Remove
void addProduct(String product) {
setState(() {
products.add(product);
});
}
void removeProduct(int index) {
setState(() {
products.removeAt(index);
});
}
Both operations change the state and therefore should be performed inside setState().
12. setState() with a Selected Item
class CategorySelector extends StatefulWidget {
const CategorySelector({super.key});
@override
State createState() => _CategorySelectorState();
}
class _CategorySelectorState extends State {
String selectedCategory = 'All';
final categories = [
'All',
'Mobile',
'Web',
'Backend',
];
@override
Widget build(BuildContext context) {
return DropdownButton(
value: selectedCategory,
items: categories.map((category) {
return DropdownMenuItem(
value: category,
child: Text(category),
);
}).toList(),
onChanged: (value) {
setState(() {
selectedCategory = value!;
});
},
);
}
}
13. setState() with TextField
A TextField can update another part of the UI when the entered value changes.
class NameScreen extends StatefulWidget {
const NameScreen({super.key});
@override
State createState() => _NameScreenState();
}
class _NameScreenState extends State {
String name = '';
@override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
onChanged: (value) {
setState(() {
name = value;
});
},
),
const SizedBox(height: 20),
Text('Hello $name'),
],
);
}
}
14. setState() and UI Rebuilding
One of the most important concepts is that setState() does not directly change the screen. Instead, it tells Flutter that the state has changed and that the framework should rebuild the affected widget.
setState(() {
count++;
});
After the state changes, Flutter executes the widget's build() method again so the UI can reflect the new state.
15. What Happens Without setState()?
Consider this code:
void increment() {
count++;
}
The variable may change internally, but Flutter has not been notified that the widget needs to rebuild.
Correct approach:
void increment() {
setState(() {
count++;
});
}
This is why forgetting setState() is a common reason for UI values not appearing to update.
16. What Code Should Go Inside setState()?
The state mutation that affects the widget should generally be placed inside the setState() callback.
Example:
setState(() {
username = 'Manish';
isLoggedIn = true;
});
The callback should describe the state change. Keep unrelated expensive operations outside it.
17. setState() Should Be Used for Local State
setState() is especially suitable when the state belongs to one widget or a small part of the widget tree.
Examples:
- Counter value
- Selected tab
- Checkbox state
- Switch state
- Animation-related UI state
- Password visibility
- Expanded/collapsed section
- Temporary form state
Flutter documentation describes this type of local state as ephemeral state.
18. Example of Ephemeral State
A selected bottom-navigation tab is a common example.
int currentIndex = 0;
BottomNavigationBar(
currentIndex: currentIndex,
onTap: (index) {
setState(() {
currentIndex = index;
});
},
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: 'Profile',
),
],
);
If only this widget needs the selected index, managing it with setState() is straightforward.
19. setState() and Application State
Technically, State and setState() can be used for all kinds of state. However, when state needs to be shared across many widgets or screens, a dedicated state-management approach can become more appropriate.
Examples of shared application state include:
- User preferences
- Login information
- Shopping cart
- Notifications
- Read/unread article status
There is no universal rule separating ephemeral state from application state; the appropriate choice depends on the application.
20. setState() vs StatelessWidget
| Feature |
StatelessWidget |
StatefulWidget |
| Mutable local state |
Not stored directly |
Stored in State object |
| setState() |
Not available |
Available in State class |
| UI changes based on internal state |
Usually no internal mutable state |
Yes |
| Example |
Static text |
Counter, switch, checkbox |
21. Complete Counter Example
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: const CounterPage(),
);
}
}
class CounterPage extends StatefulWidget {
const CounterPage({super.key});
@override
State createState() => _CounterPageState();
}
class _CounterPageState extends State {
int counter = 0;
void increment() {
setState(() {
counter++;
});
}
void decrement() {
setState(() {
if (counter > 0) {
counter--;
}
});
}
void reset() {
setState(() {
counter = 0;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Counter State Management'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Counter Value',
style: TextStyle(fontSize: 20),
),
Text(
'$counter',
style: const TextStyle(
fontSize: 48,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: decrement,
child: const Text('-'),
),
const SizedBox(width: 10),
ElevatedButton(
onPressed: reset,
child: const Text('Reset'),
),
const SizedBox(width: 10),
ElevatedButton(
onPressed: increment,
child: const Text('+'),
),
],
),
],
),
),
);
}
}
22. Multiple State Variables
A single StatefulWidget can contain multiple state variables.
class _ProfileState extends State {
String name = 'John';
int age = 25;
bool isOnline = false;
void updateProfile() {
setState(() {
name = 'Alex';
age = 30;
isOnline = true;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(name),
Text('$age'),
Text(isOnline ? 'Online' : 'Offline'),
],
);
}
}
Multiple related state values can be changed inside the same setState() call.
23. setState() and Conditional UI
setState() is frequently used to display different widgets depending on the current state.
bool isLoading = false;
void loadData() {
setState(() {
isLoading = true;
});
// Perform asynchronous work...
}
The UI can then use:
isLoading
? const CircularProgressIndicator()
: const Text('Data Loaded');
In real applications, asynchronous operations should also update success and error states appropriately.
24. setState() with Async Operations
A common pattern is to update loading state before an asynchronous operation and update the result afterward.
bool isLoading = false;
String message = '';
Future loadData() async {
setState(() {
isLoading = true;
message = '';
});
try {
await Future.delayed(const Duration(seconds: 2));
setState(() {
message = 'Data loaded successfully';
isLoading = false;
});
} catch (error) {
setState(() {
message = 'Something went wrong';
isLoading = false;
});
}
}
For larger applications, more structured approaches such as ChangeNotifier or other state-management solutions can separate this state logic from the UI.
25. Avoid Unnecessary setState() Calls
Do not call setState() when no relevant UI state has changed.
Unnecessary example:
setState(() {
print('Hello');
});
The code above does not modify UI state, so using setState() provides no useful state-management purpose.
Better:
print('Hello');
26. Keep setState() Focused
Prefer small and clear state updates.
Good:
setState(() {
count++;
});
Avoid putting unrelated heavy operations inside setState().
For example, network requests, large file processing, or expensive calculations should generally be handled outside the state mutation callback when possible.
27. setState() and Widget Lifecycle
Stateful widgets have a lifecycle. Common lifecycle methods include:
initState()
didChangeDependencies()
build()
didUpdateWidget()
setState()
deactivate()
dispose()
setState() is used when a value stored by the State object changes and the UI needs to reflect that change.
28. Important Rule: Check mounted for Async Updates
When an asynchronous operation finishes later, the widget may have already been removed from the widget tree. Before calling setState() after an awaited operation, check whether the State object is still mounted when appropriate.
Future loadData() async {
final result = await fetchData();
if (!mounted) {
return;
}
setState(() {
data = result;
});
}
This helps avoid attempting to update a State object that is no longer mounted.
29. Common Mistake: Changing State Outside setState()
Incorrect:
void increment() {
counter++;
}
Correct:
void increment() {
setState(() {
counter++;
});
}
30. Common Mistake: Calling setState() in build()
Calling setState() directly during the build() process can cause repeated rebuilds and should generally be avoided.
Instead, change state in response to events, lifecycle callbacks, or completed asynchronous operations as appropriate.
31. Common Mistake: Using setState() for Shared Application State
If many unrelated widgets need access to the same state, keeping that state deep inside one widget and passing callbacks through many levels can become difficult to maintain.
In such situations, Flutter provides other approaches such as:
ValueNotifier and InheritedNotifier
InheritedWidget and InheritedModel
ChangeNotifier
- Community state-management packages
setState() is a built-in low-level approach, while other built-in and community approaches can be useful for more complex cases.
32. setState() vs ChangeNotifier
| Feature |
setState() |
ChangeNotifier |
| Main purpose |
Local widget state |
Share and notify about state changes |
| Location |
State class |
Separate state/ViewModel class |
| Complexity |
Simple |
More structured |
| Suitable for |
Small/local UI state |
Shared or more complex state |
| Notification method |
setState() |
notifyListeners() |
ChangeNotifier can notify listening widgets when its data changes, while setState() is directly associated with a particular State object.
33. setState() and Provider
Provider is a community package often used with ChangeNotifier for application state. It is different from simply using setState().
For small isolated state, setState() may be sufficient. For state shared across multiple widgets, a more structured approach can reduce callback and state-passing complexity.
Flutter's simple state-management guide demonstrates Provider with ChangeNotifier, ChangeNotifierProvider, and Consumer.
34. Parent and Child State Management
Sometimes the parent widget should manage state and pass the current value to a child.
class ParentWidget extends StatefulWidget {
const ParentWidget({super.key});
@override
State createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State {
bool active = false;
void handleChanged(bool value) {
setState(() {
active = value;
});
}
@override
Widget build(BuildContext context) {
return ChildWidget(
active: active,
onChanged: handleChanged,
);
}
}
In this pattern, the parent owns the state and uses setState() to update it. Flutter's interactivity guide describes widget-owned, parent-owned, and mixed approaches to managing state.
35. Practical Example: Favorite Button
class FavoriteButton extends StatefulWidget {
const FavoriteButton({super.key});
@override
State createState() => _FavoriteButtonState();
}
class _FavoriteButtonState extends State {
bool isFavorite = false;
@override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(
isFavorite
? Icons.favorite
: Icons.favorite_border,
),
onPressed: () {
setState(() {
isFavorite = !isFavorite;
});
},
);
}
}
This is a good example of local state because the favorite button can manage its own temporary visual state.
36. Practical Example: Expand and Collapse
class ExpandableSection extends StatefulWidget {
const ExpandableSection({super.key});
@override
State createState() => _ExpandableSectionState();
}
class _ExpandableSectionState extends State {
bool expanded = false;
@override
Widget build(BuildContext context) {
return Column(
children: [
ListTile(
title: const Text('Flutter'),
trailing: IconButton(
icon: Icon(
expanded
? Icons.expand_less
: Icons.expand_more,
),
onPressed: () {
setState(() {
expanded = !expanded;
});
},
),
),
if (expanded)
const Padding(
padding: EdgeInsets.all(16),
child: Text(
'Flutter is a UI toolkit for building applications.',
),
),
],
);
}
}
37. Practical Example: Like Counter
class LikeButton extends StatefulWidget {
const LikeButton({super.key});
@override
State createState() => _LikeButtonState();
}
class _LikeButtonState extends State {
int likes = 0;
void likePost() {
setState(() {
likes++;
});
}
@override
Widget build(BuildContext context) {
return Row(
children: [
IconButton(
onPressed: likePost,
icon: const Icon(Icons.thumb_up),
),
Text('$likes likes'),
],
);
}
}
38. Advantages of setState()
- Very easy to understand.
- Built directly into Flutter.
- No external package is required.
- Excellent for local widget state.
- Requires very little boilerplate.
- Easy for beginners to learn.
- Works naturally with StatefulWidget.
- Useful for interactive UI components.
39. Limitations of setState()
- Can become difficult to manage when state is shared widely.
- Large widgets can become difficult to maintain if they contain too much state logic.
- Passing state through many widget levels can become cumbersome.
- Complex business logic may be better separated from the UI.
- Large applications may benefit from a more structured state-management architecture.
40. Best Practices for setState()
- Use
setState() for state that belongs to the current widget.
- Keep the state mutation inside the
setState() callback.
- Keep expensive operations outside the callback when possible.
- Use meaningful state variable names.
- Keep StatefulWidgets focused and reasonably small.
- Do not call
setState() when nothing relevant changed.
- Be careful with asynchronous operations and widget disposal.
- Move widely shared state to a more suitable architecture when necessary.
41. When Should You Use setState()?
| Situation |
Recommended Approach |
| Counter inside one screen |
setState() |
| Checkbox state |
setState() |
| Password visibility |
setState() |
| Selected tab used by one widget |
setState() |
| Expandable section |
setState() |
| Shopping cart shared across many screens |
Consider application state management |
| Authentication state across the application |
Consider a shared state architecture |
| Complex business state |
Consider a structured state-management approach |
42. setState() State Management Architecture
StatefulWidget
↓
State Object
↓
User Interaction
↓
setState()
↓
State Changes
↓
build()
↓
Updated Widgets
↓
Updated UI
43. Important Difference: State Change vs UI Update
It is important to understand that changing a variable and rebuilding the UI are two related but distinct ideas.
counter++;
This changes the variable.
setState(() {
counter++;
});
This changes the variable and informs Flutter that the widget needs to rebuild.
44. Interview Questions
Q1. What is setState() in Flutter?
Answer: setState() is a method of the State class that tells Flutter that the widget's internal state has changed and that its UI should be rebuilt.
Q2. Where can setState() be used?
Answer: It is used from a State object associated with a StatefulWidget.
Q3. What happens when setState() is called?
Answer: Flutter marks the State object as needing to rebuild and subsequently runs its build() method so the UI can reflect the updated state.
Q4. What happens if you change state without setState()?
Answer: The state variable can change internally, but Flutter is not notified that the widget needs to rebuild, so the UI may not reflect the new value.
Q5. Is setState() suitable for every application?
Answer: It can technically manage many types of state, but it is especially suitable for local or widget-specific state. Larger applications may benefit from more structured state-management approaches.
Q6. Can multiple variables be changed inside setState()?
Answer: Yes. Multiple related state variables can be changed within the same setState() callback.
Q7. Is setState() a package?
Answer: No. setState() is part of Flutter's built-in StatefulWidget/State mechanism.
45. Quick Revision
setState() is used to notify Flutter about local state changes.
- It is normally used inside a
StatefulWidget's State class.
- State variables should be updated inside the
setState() callback.
- Calling
setState() causes the relevant widget to be scheduled for rebuilding.
- The
build() method then reflects the new state in the UI.
- It is ideal for simple, local, widget-specific state.
- It is not necessary to use a third-party package for simple state management.
- For widely shared or complex state, consider a more structured state-management solution.
46. Learning Outcome
After studying setState() state management, you should be able to:
- Understand what state means in Flutter.
- Explain the purpose of
setState().
- Create and use StatefulWidgets.
- Update integer, boolean, string, and list state.
- Build interactive counters, forms, switches, checkboxes, and buttons.
- Understand how state changes trigger UI rebuilding.
- Avoid common
setState() mistakes.
- Recognize when local state should move to a shared state-management solution.
47. Useful Flutter Resources
48. JustAcademy Flutter Resources
For structured Flutter learning and practical development training, explore the following resources:
49. Summary
setState() is a fundamental Flutter state-management mechanism for updating local widget state. A StatefulWidget stores mutable data inside its associated State object. When that data changes, calling setState() tells Flutter that the widget needs to be rebuilt so the UI can display the latest state.
For simple interactions such as counters, checkboxes, switches, selected tabs, password visibility, expandable sections, and other widget-specific behavior, setState() is often an effective and straightforward solution. As an application grows and state needs to be shared or organized across many parts of the application, a more structured state-management approach may become appropriate.