setState() in Flutter
setState() is one of the most important concepts in Flutter's local state management.
It is used inside a State object to tell the Flutter framework that some mutable state has
changed and that the widget's UI should be rebuilt to reflect the new value.
In Flutter, changing a state variable by itself does not automatically tell the framework to rebuild
the widget. When the state changes, setState() is used to notify Flutter so that the
relevant build() method can run again. :contentReference[oaicite:0]{index=0}
For professional Flutter learning and training, visit
JustAcademy Flutter Training
and use the
Flutter Course Demo Registration
page.
1. What is setState()?
setState() is a method provided by Flutter's State class. It is used to
indicate that the internal state of a StatefulWidget has changed.
The basic syntax is:
setState(() {
// Change your state here
});
When setState() is called, Flutter marks the State object as needing to be rebuilt and
schedules the UI update. The build() method can then run again using the updated state. :contentReference[oaicite:1]{index=1}
2. Why is setState() Needed?
Flutter uses a declarative UI approach. Instead of directly modifying an already-rendered widget,
you change the data that describes the UI and Flutter rebuilds the appropriate widget subtree.
For example:
int count = 0;
Suppose you change it like this:
count++;
The variable changes in memory, but Flutter is not automatically notified that the UI needs to be
rebuilt.
The preferred local-state approach is:
setState(() {
count++;
});
Now Flutter knows that the State object has changed and can rebuild the widget so the new value can
appear on the screen. :contentReference[oaicite:2]{index=2}
3. Basic setState() Flow
User Interaction
|
v
Event Handler
|
v
Change State
|
v
setState()
|
v
Flutter schedules rebuild
|
v
build() runs again
|
v
Updated UI
For example:
Button pressed
↓
count changes
↓
setState()
↓
build()
↓
Text displays new count
4. Simple Counter Example
The counter is one of the easiest examples for understanding setState().
import 'package:flutter/material.dart';
class CounterPage extends StatefulWidget {
const CounterPage({super.key});
@override
State<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int count = 0;
void increment() {
setState(() {
count++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Counter Example'),
),
body: Center(
child: Text(
'Count: $count',
style: const TextStyle(fontSize: 30),
),
),
floatingActionButton: FloatingActionButton(
onPressed: increment,
child: const Icon(Icons.add),
),
);
}
}
How It Works
- The initial value of
count is 0.
- The value is displayed using
Text('Count: $count').
- The user presses the FloatingActionButton.
- The
increment() method is called.
setState() increments count.
- Flutter schedules the State object for rebuilding.
- The
build() method runs again.
- The updated count appears on the screen.
5. setState() Syntax
The most common form is:
setState(() {
variable = newValue;
});
Example:
bool isLoggedIn = false;
setState(() {
isLoggedIn = true;
});
Another example:
setState(() {
count = count + 1;
});
Or:
setState(() {
count++;
});
6. setState() with Boolean Values
Boolean state is commonly used for toggles, visibility, selection, and other two-state UI behavior.
class ToggleExample extends StatefulWidget {
const ToggleExample({super.key});
@override
State<ToggleExample> createState() => _ToggleExampleState();
}
class _ToggleExampleState extends State<ToggleExample> {
bool isActive = false;
void toggle() {
setState(() {
isActive = !isActive;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
isActive ? 'Active' : 'Inactive',
style: const TextStyle(fontSize: 24),
),
ElevatedButton(
onPressed: toggle,
child: const Text('Toggle'),
),
],
);
}
}
Every time the button is pressed, isActive changes between true and
false, and setState() causes the UI to reflect the new value.
7. setState() with Checkbox
class CheckboxExample extends StatefulWidget {
const CheckboxExample({super.key});
@override
State<CheckboxExample> createState() => _CheckboxExampleState();
}
class _CheckboxExampleState extends State<CheckboxExample> {
bool isChecked = false;
@override
Widget build(BuildContext context) {
return Checkbox(
value: isChecked,
onChanged: (value) {
setState(() {
isChecked = value ?? false;
});
},
);
}
}
The checkbox's current value is stored in isChecked. When the user changes the
checkbox, setState() updates the variable and rebuilds the relevant UI.
8. setState() with Switch
class SwitchExample extends StatefulWidget {
const SwitchExample({super.key});
@override
State<SwitchExample> createState() => _SwitchExampleState();
}
class _SwitchExampleState extends State<SwitchExample> {
bool isEnabled = false;
@override
Widget build(BuildContext context) {
return Switch(
value: isEnabled,
onChanged: (value) {
setState(() {
isEnabled = value;
});
},
);
}
}
9. setState() with a Slider
class SliderExample extends StatefulWidget {
const SliderExample({super.key});
@override
State<SliderExample> createState() => _SliderExampleState();
}
class _SliderExampleState extends State<SliderExample> {
double value = 50;
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
'Value: ${value.toInt()}',
style: const TextStyle(fontSize: 22),
),
Slider(
value: value,
min: 0,
max: 100,
onChanged: (newValue) {
setState(() {
value = newValue;
});
},
),
],
);
}
}
The slider value changes continuously as the user moves the slider. Each state update is wrapped in
setState() so that the displayed value can update.
10. setState() with TextField
A TextField can work with a TextEditingController when the application needs
to read or manipulate the entered text.
class TextExample extends StatefulWidget {
const TextExample({super.key});
@override
State<TextExample> createState() => _TextExampleState();
}
class _TextExampleState extends State<TextExample> {
final TextEditingController controller =
TextEditingController();
String name = '';
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
controller: controller,
decoration: const InputDecoration(
labelText: 'Enter your name',
),
),
ElevatedButton(
onPressed: () {
setState(() {
name = controller.text;
});
},
child: const Text('Show Name'),
),
Text(
'Hello $name',
style: const TextStyle(fontSize: 24),
),
],
);
}
}
11. setState() with a List
You can use setState() when adding, removing, or modifying items in a local list.
class TodoExample extends StatefulWidget {
const TodoExample({super.key});
@override
State<TodoExample> createState() => _TodoExampleState();
}
class _TodoExampleState extends State<TodoExample> {
final List<String> tasks = [];
void addTask() {
setState(() {
tasks.add('New Task');
});
}
void removeTask(int index) {
setState(() {
tasks.removeAt(index);
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(
onPressed: addTask,
child: const Text('Add Task'),
),
Expanded(
child: ListView.builder(
itemCount: tasks.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(tasks[index]),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
removeTask(index);
},
),
);
},
),
),
],
);
}
}
12. What Happens When setState() is Called?
Calling setState() does not directly mean that Flutter immediately redraws every widget
in the application. Instead, it informs Flutter that the particular State object has changed and
needs to participate in the next appropriate rebuild.
setState(() {
count++;
});
The conceptual process is:
- The callback passed to
setState() executes.
- The State object is marked as needing to rebuild.
- Flutter schedules the update.
- The State object's
build() method runs again.
- The UI is generated from the new state.
Flutter's official documentation describes setState() as the mechanism that signals the
framework to update the UI when mutable State changes. :contentReference[oaicite:3]{index=3}
13. setState() Does Not Store State
An important concept is that setState() itself does not store your data. Your variables
store the state. setState() tells Flutter that those values have changed and the UI
should be rebuilt.
int count = 0; // State
setState(() { // Notification to Flutter
count++;
});
In this example:
count is the state.
count++ changes the state.
setState() notifies Flutter about the change.
build() uses the updated value to construct the UI.
14. Without setState() vs With setState()
Without setState() |
With setState() |
|---|
State variable may change |
State variable changes |
Flutter is not notified through setState() |
Flutter is notified |
UI may remain unchanged |
Flutter rebuilds the State's UI |
Not the normal pattern for local UI state changes |
Standard pattern for local StatefulWidget state |
Without setState()
void increment() {
count++;
}
With setState()
void increment() {
setState(() {
count++;
});
}
15. What Should Go Inside setState()?
The callback passed to setState() should contain the synchronous state mutation that
needs to be reflected in the UI.
Good example:
setState(() {
isFavorite = !isFavorite;
});
Another example:
setState(() {
cartItems.add(product);
});
The purpose is to make the state change explicit and immediately notify Flutter about it.
16. Avoid Unnecessary Work Inside setState()
Keep the callback passed to setState() focused on the state mutation. Do not put
unrelated expensive work inside it.
Prefer:
final result = calculateSomething();
setState(() {
value = result;
});
Instead of putting unnecessary computation inside:
setState(() {
value = calculateSomething();
});
The important idea is to keep the state-change callback small and focused.
17. Multiple State Variables
Multiple related state variables can be updated inside one setState() call.
bool isLoading = false;
String message = '';
void loadData() {
setState(() {
isLoading = true;
message = 'Loading...';
});
}
This makes the related state changes part of the same update.
18. Example: Login Button State
class LoginButton extends StatefulWidget {
const LoginButton({super.key});
@override
State<LoginButton> createState() => _LoginButtonState();
}
class _LoginButtonState extends State<LoginButton> {
bool isLoading = false;
Future<void> login() async {
setState(() {
isLoading = true;
});
await Future.delayed(
const Duration(seconds: 2),
);
if (!mounted) {
return;
}
setState(() {
isLoading = false;
});
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: isLoading ? null : login,
child: isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(),
)
: const Text('Login'),
);
}
}
This example demonstrates a loading state. The button becomes disabled while the asynchronous
operation is in progress.
19. setState() and Async Operations
When asynchronous code finishes, the State object might no longer be mounted. Therefore, when an
asynchronous operation is expected to update State, check mounted before calling
setState() if the State may have been disposed during the operation.
Future<void> loadData() async {
final result = await fetchData();
if (!mounted) {
return;
}
setState(() {
data = result;
});
}
This is particularly important for network requests, timers, delayed operations, and other
asynchronous tasks.
20. setState() and StatefulWidget
setState() is normally used inside the State class of a StatefulWidget.
class Counter extends StatefulWidget {
const Counter({super.key});
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int count = 0;
void increment() {
setState(() {
count++;
});
}
@override
Widget build(BuildContext context) {
return Text('$count');
}
}
The StatefulWidget provides the structure, while the State object contains the mutable data and
calls setState() when that data changes.
21. setState() and build()
A common misconception is that setState() directly changes the text, color, size, or
other UI properties. It does not directly manipulate the displayed widget.
Instead, it causes the State's build() method to be scheduled for rebuilding, and the
build method describes the UI based on the new state.
int count = 0;
void increment() {
setState(() {
count++;
});
}
@override
Widget build(BuildContext context) {
return Text('$count');
}
The important relationship is:
State changes
↓
setState()
↓
build()
↓
New UI configuration
22. Example: Favorite Button
class FavoriteButton extends StatefulWidget {
const FavoriteButton({super.key});
@override
State<FavoriteButton> createState() => _FavoriteButtonState();
}
class _FavoriteButtonState extends State<FavoriteButton> {
bool isFavorite = false;
@override
Widget build(BuildContext context) {
return IconButton(
onPressed: () {
setState(() {
isFavorite = !isFavorite;
});
},
icon: Icon(
isFavorite
? Icons.favorite
: Icons.favorite_border,
),
);
}
}
This is a practical example of local state: the widget only needs to remember whether its own
favorite icon is selected.
23. Example: Password Visibility
class PasswordField extends StatefulWidget {
const PasswordField({super.key});
@override
State<PasswordField> createState() => _PasswordFieldState();
}
class _PasswordFieldState extends State<PasswordField> {
bool obscureText = true;
@override
Widget build(BuildContext context) {
return TextField(
obscureText: obscureText,
decoration: InputDecoration(
labelText: 'Password',
suffixIcon: IconButton(
icon: Icon(
obscureText
? Icons.visibility
: Icons.visibility_off,
),
onPressed: () {
setState(() {
obscureText = !obscureText;
});
},
),
),
);
}
}
24. Example: Shopping Cart Quantity
class ProductQuantity extends StatefulWidget {
const ProductQuantity({super.key});
@override
State<ProductQuantity> createState() => _ProductQuantityState();
}
class _ProductQuantityState extends State<ProductQuantity> {
int quantity = 1;
void increase() {
setState(() {
quantity++;
});
}
void decrease() {
if (quantity > 1) {
setState(() {
quantity--;
});
}
}
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
onPressed: decrease,
icon: const Icon(Icons.remove),
),
Text(
'$quantity',
style: const TextStyle(fontSize: 22),
),
IconButton(
onPressed: increase,
icon: const Icon(Icons.add),
),
],
);
}
}
25. setState() and Local State
setState() is especially suitable for small pieces of state that belong to one widget
and do not need to be shared throughout the application.
Examples include:
- Current tab index
- Checkbox selection
- Favorite status
- Password visibility
- Temporary form state
- Animation-related local values
- Current slider value
- Expanded/collapsed state
Flutter documentation refers to this type of widget-specific state as ephemeral or local state.
For state that needs to be shared across multiple parts of an application, broader state-management
approaches may be more appropriate. :contentReference[oaicite:4]{index=4}
26. setState() and Parent-Child Communication
Sometimes the parent widget owns the state and the child widget only receives the current value and
a callback.
class ParentWidget extends StatefulWidget {
const ParentWidget({super.key});
@override
State<ParentWidget> createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
bool isActive = false;
void updateActive(bool value) {
setState(() {
isActive = value;
});
}
@override
Widget build(BuildContext context) {
return ChildWidget(
isActive: isActive,
onChanged: updateActive,
);
}
}
class ChildWidget extends StatelessWidget {
final bool isActive;
final ValueChanged<bool> onChanged;
const ChildWidget({
super.key,
required this.isActive,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
return Switch(
value: isActive,
onChanged: onChanged,
);
}
}
Here, the parent owns the state and uses setState(). The child requests a change by
calling the callback.
27. Calling setState() Too Frequently
setState() should be used when the UI actually needs to reflect a local state change.
Calling it unnecessarily can cause additional rebuild work.
For example, avoid repeatedly calling:
setState(() {
// No meaningful state change
});
Instead, make the state changes clear and intentional.
28. setState() Should Not Be Used Everywhere
setState() is useful for local state, but it is not a universal state-management
solution for every application.
When state needs to be shared across many unrelated widgets or managed across larger application
features, Flutter applications can use other state-management techniques and architectures.
The important principle is to choose a state-management approach based on the scope and complexity
of the state.
29. Common Mistakes with setState()
Mistake 1: Changing State Without setState()
count++;
If the change should update the widget's UI, wrap the mutation in setState().
setState(() {
count++;
});
Mistake 2: Calling setState() Outside a State Object
setState() belongs to the State class. It is normally called from methods
associated with the StatefulWidget's State object.
Mistake 3: Updating State After dispose()
An asynchronous operation may finish after the widget has been removed. Check mounted
before updating State when necessary.
if (!mounted) {
return;
}
setState(() {
isLoading = false;
});
Mistake 4: Putting Expensive Work Inside setState()
Keep the callback focused on changing the state that needs to trigger the rebuild.
Mistake 5: Calling setState() in build()
Avoid triggering a state update directly from build(). Doing so can create repeated
rebuilds or other undesirable behavior.
30. setState() with Timer
A timer can update local state periodically. If the timer changes UI state, the update can be
wrapped in setState().
Timer? timer;
int seconds = 0;
@override
void initState() {
super.initState();
timer = Timer.periodic(
const Duration(seconds: 1),
(timer) {
if (!mounted) {
timer.cancel();
return;
}
setState(() {
seconds++;
});
},
);
}
@override
void dispose() {
timer?.cancel();
super.dispose();
}
When using timers, remember to cancel them during dispose() so they do not continue
running after the State object is no longer needed.
31. Complete setState() Example
import 'package:flutter/material.dart';
void main() {
runApp(const MaterialApp(
home: CounterScreen(),
));
}
class CounterScreen extends StatefulWidget {
const CounterScreen({super.key});
@override
State<CounterScreen> createState() => _CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
int count = 0;
void increase() {
setState(() {
count++;
});
}
void decrease() {
if (count > 0) {
setState(() {
count--;
});
}
}
void reset() {
setState(() {
count = 0;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('setState Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Current Count',
style: TextStyle(fontSize: 20),
),
Text(
'$count',
style: const TextStyle(
fontSize: 50,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: decrease,
child: const Text('Decrease'),
),
const SizedBox(width: 10),
ElevatedButton(
onPressed: increase,
child: const Text('Increase'),
),
const SizedBox(width: 10),
ElevatedButton(
onPressed: reset,
child: const Text('Reset'),
),
],
),
],
),
),
);
}
}
Features Demonstrated
- State variable
- StatefulWidget
- State class
setState()
- Increment operation
- Decrement operation
- Reset operation
- UI rebuilding
32. setState() Best Practices
- Use
setState() when local mutable state changes and the UI needs to reflect it.
- Keep the callback passed to
setState() small and focused.
- Do not perform unnecessary expensive operations inside
setState().
- Do not call
setState() unnecessarily.
- Keep state close to the widget that owns it when practical.
- Use
mounted appropriately when asynchronous operations update State.
- Dispose controllers, timers, and other resources when required.
- Avoid calling
setState() directly from build().
- Use broader state-management approaches when application state needs to be shared extensively.
- Keep your
build() method focused on describing the UI from current state.
33. Interview Questions on setState()
Q1. What is setState() in Flutter?
setState() is a method of the State class that tells Flutter that mutable
state has changed and that the widget should be rebuilt.
Q2. Why is setState() required?
Flutter needs to be notified when local mutable state changes. setState() provides that
notification and schedules the State object for rebuilding.
Q3. What happens if you change a state variable without setState()?
The variable can change internally, but Flutter is not notified through setState(), so
the UI may not rebuild to display the new value.
Q4. Where can setState() normally be called?
It is normally called from the State class of a StatefulWidget.
Q5. Does setState() immediately rebuild the entire application?
No. It marks the relevant State object as needing a rebuild and Flutter schedules the appropriate
update.
Q6. Can setState() be used for application-wide state?
It can technically be used in a State object, but for state that must be shared broadly across an
application, other state-management approaches may be more appropriate.
Q7. What is the relationship between setState() and build()?
Calling setState() signals that the State changed, which causes Flutter to schedule the
State's build() method to run again so the UI can reflect the updated state.
Q8. Can multiple variables be changed inside one setState()?
Yes. Multiple related state variables can be updated inside a single setState()
callback.
34. Practice Exercise
Create a Flutter application with the following features:
- Create a StatefulWidget called
StudentProfile.
- Create a variable named
studentName.
- Display the student name.
- Add a button to change the name.
- Use
setState() when changing the name.
- Create a boolean variable named
isPresent.
- Add a Switch to change attendance status.
- Display either Present or Absent.
- Add a counter for completed assignments.
- Use
setState() to increment the counter.
Expected Structure
StudentProfile
|
├── studentName
|
├── isPresent
|
└── assignments
|
v
setState()
|
v
build()
|
v
Updated UI
35. Quick Revision Table
Concept |
Explanation |
|---|
State |
Data that can change during the lifetime of a widget. |
StatefulWidget |
A widget used when dynamic local state is needed. |
State |
Object that stores mutable state and implements the UI build method. |
setState() |
Notifies Flutter that State changed and the UI needs rebuilding. |
build() |
Describes the UI based on the current state. |
mounted |
Useful for checking whether the State is still associated with the widget tree. |
dispose() |
Used to clean up resources when the State is removed. |
36. Key Takeaways
setState() is fundamental for managing simple local state in Flutter.
- It is called from the State object of a StatefulWidget.
- State variables contain the actual changing data.
setState() tells Flutter that the State has changed.
- Flutter then schedules the relevant widget to rebuild.
- The
build() method uses the new state to describe the updated UI.
- Changing a state variable without notifying Flutter may leave the visible UI unchanged.
- Keep the
setState() callback small and focused.
- Use
mounted carefully when asynchronous operations update State.
- Use other state-management approaches when state needs to be shared broadly across an application.
37. Learning Resources
Conclusion
setState() is the basic mechanism Flutter provides for telling a StatefulWidget that
its local mutable state has changed. The important pattern to remember is:
Change State
↓
Call setState()
↓
Flutter schedules rebuild
↓
build() runs again
↓
Updated UI
Once you understand this pattern, you can create interactive Flutter applications such as counters,
forms, shopping carts, toggles, checkboxes, sliders, loading screens, favorite buttons, and many
other dynamic interfaces.