Flutter Tutorial
setState in Flutter — Exposed!

We all know that setState function is used for rebuilding our widget tree whenever there is a change in the internal state of the widget. And it is very straightforward to use it. But have you ever noticed the below two scenarios
// Scenario 1
setState(() {
currentPage++;
});
// Scenario 2
currentPage++;
setState(() {});
Both works exactly the same. Increments the page and refreshes the UI.
But then why we have a callback in the setState function? What does it do and how setState is working behind the scenes? I was asked this question and I didn’t know it at then so tried looking for answers and would like to share what I understood.
setState use case
The definition of the setState function is
void setState(VoidCallback fn) { }
It takes a void callback and it calls it immediately and synchronously. So if we want to refresh the UI but don’t call the setState function, there will be no change anywhere.
What happens when setState is called
The setState function tells the Flutter framework that the internal state of the widget is changed and can be refreshed. Ok! But how it actually notifies the framework? Read on..
@protected
void setState(VoidCallback fn) {
// ...
if (_debugLifecycleState == _StateLifecycle.defunct) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('setState() called after dispose(): $this'),
ErrorDescription(
'This error happens if you call setState() on a State object for a widget that '
'no longer appears in the widget tree (e.g., whose parent widget no longer '
'includes the widget in its build). This error can occur when code calls '
'setState() from a timer or an animation callback.',
),
ErrorHint(
'The preferred solution is '
'to cancel the timer or stop listening to the animation in the dispose() '
'callback. Another solution is to check the "mounted" property of this '
'object before calling setState() to ensure the object is still in the '
'tree.',
),
ErrorHint(
'This error might indicate a memory leak if setState() is being called '
'because another object is retaining a reference to this State object '
'after it has been removed from the tree. To avoid memory leaks, '
'consider breaking the reference to this object during dispose().',
),
]);
}
if (_debugLifecycleState == _StateLifecycle.created && !mounted) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('setState() called in constructor: $this'),
ErrorHint(
'This happens when you call setState() on a State object for a widget that '
"hasn't been inserted into the widget tree yet. It is not necessary to call "
'setState() in the constructor, since the state is already assumed to be dirty '
'when it is initially created.',
),
]);
}
final Object? result = fn() as dynamic;
assert(() {
if (result is Future) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('setState() callback argument returned a Future.'),
ErrorDescription(
'The setState() method on $this was called with a closure or method that '
'returned a Future. Maybe it is marked as "async".',
),
ErrorHint(
'Instead of performing asynchronous work inside a call to setState(), first '
'execute the work (without updating the widget state), and then synchronously '
'update the state inside a call to setState().',
),
]);
}
return true;
}());
_element!.markNeedsBuild();
}
Above code snippet is taken from official setState documentation.
https://api.flutter.dev/flutter/widgets/State/setState.html
Once we call the setState, internally it does some sanity checks like
- the widget is mounted or not
- dispose call is made or not
If neither of the above is true then it executes our callback and then it calls the markNeedsBuild() function. That is it. This is the only execution setState function does.
Now what is this new function markNeedsBuild? Let’s check it.
markNeedsBuild
Earlier, markNeedsBuild function was used. It didn’t have any callback. It marks the element as dirty (i.e its state is updated and ready to be rebuilt) and add it to the list of widgets to be rebuilt in next frame.
markNeedsBuild drawback
Developers were not sure when the update made on the state would refresh the UI, so they used to call markNeedsBuild function more than necessary.
If we call markNeedsBuild multiple times in one frame then it would build same element multiple times which is inefficient. We needed a way to mark the element as dirty once per frame so that it can be built only once per frame.
Hence, a callback is added and setState was formed.
But how does this improved the performance?
Performance aspect
setState function is idempotent which means that the output won’t change even if you call it repeatedly in a frame and when state is changed only once. So calling setState directly has no performance cost as such.
But it does have a cost associated with it due to widget rebuilds. So it is advised to be called only when state is updated meaningfully.
So what’s the difference?
// Scenario 1
setState(() {
currentPage++;
});
// Scenario 2
currentPage++;
setState(() {});
There is no difference. We saw that calling setState tells the Flutter framework to rebuild the UI and that is why both of the above scenarios works same.
But it is advised to
- update the state within the callback so that developers know what is exactly updated
- in case of listeners or animation controllers, if the setState is called, there should be a comment added in its callback that animation state is being updated
- not perform any computational part in this callback as it would only delay the UI updates
Conclusion
setState callback is there for developers to carefully make the UI updates and make the state changes in the callback to know what is updated and when. Behaviour wise, our scenarios works same.