Two situations
Can setState() be called directly in build()? The answer is whether it can or cannot.
Let's take a look at a simple code:
import 'package:flutter/'; class TestPage extends StatefulWidget { const TestPage({}); @override State<TestPage> createState() => _State(); } class _State extends State<TestPage> { int _count = 0; @override Widget build(BuildContext context) { setState(() { _count++; }); return Scaffold( appBar: AppBar( title: const Text('Test Page'), ), body: Center( child: Text( '$_count', style: const TextStyle(fontSize: 24), ), ), ); } }
The code will not report an error after running. Text('$_count') shows that the result is 1. It seems that there is no problem with the build() calling setState(). A little change, let’s take a look at this:
class _State extends State&lt;TestPage&gt; { int _count = 0; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Test Page'), ), body: Center( child: Builder( builder: (context) { setState(() { _count++; }); return Text( '$_count', style: const TextStyle(fontSize: 24), ); } ), ), ); } }
The main change is to add a Builder on Text, and then put setState() in the Builder builder to call. When running, an error occurred:The following assertion was thrown building Builder(dirty): setState() or markNeedsBuild() called during build.
A prompt shows that an assertion error occurred during the build() process of Builder: setState() or markNeedsBuild() cannot be called in build().
What is this situation? Why can setState() be called in build() in the first case but not in the second case? Let’s briefly analyze the principles contained in it.
Principle analysis
Let’s talk about the conclusion first. Calling setState() directly in build() will meet a prerequisite:
If the component A is currently in build(), then the component that setState() causes rebuild must be A or descendant of A, and cannot be A's ancestors.
This is because the order of component builds is from parent to child. If setState() is executed during the child component build process, it will cause the parent component to rebuild, then the dead loop will definitely not work.
Next, let’s take a look at how to judge and control the Flutter source code. setState() will be called internally_element!.markNeedsBuild()
,markNeedsBuild()
There are the following codes:
void markNeedsBuild() { // ... // In the first half, assert whether rebuilding satisfies the premise mentioned above. assert(() { if (owner!._debugBuilding) { assert(owner!._debugCurrentBuildTarget != null); assert(owner!._debugStateLocked); // _debugIsInScope() is used to determine whether the prerequisites are met. if (_debugIsInScope(owner!._debugCurrentBuildTarget!)) { return true; } if (!_debugAllowIgnoredCallsToMarkNeedsBuild) { final List&lt;DiagnosticsNode&gt; information = &lt;DiagnosticsNode&gt;[ ErrorSummary('setState() or markNeedsBuild() called during build.'), // ... ]; // ... } // ... }()); // ... }
The first half of markNeedsBuild() code has assertions to deal with whether the prerequisites mentioned above are met._debugCurrentBuildTarget
It is the element currently in the build state._debugCurrentBuildTarget()
The contents are as follows:
bool _debugIsInScope(Element target) { Element? current = this; while (current != null) { if (target == current) { return true; } current = current._parent; } return false; }
_debugIsInScope()this
It is the component that calls setState() to cause rebuild.target
It is the component currently being built. Among themwhile
The loop will gradually compare the current and its parent component's current build object, and will return true only if it is found, otherwise it will be false. If it is false, an error will appear in the following assertion:setState() or markNeedsBuild() called during build.
If there is currently a component that is building, then the parent component must not be rebuilded. Let’s take a look at the second case of the error reported in the previous example. Builder is a child component of TestPage. The setState called in the builder method of Builder is on TestPage, that is, the parent component is rebuilded during the build process of the child component, which will cause the assertion failure; in the first case, the setState is called in the build process of TestPage to rebuild itself, which can satisfy the premise of the conclusion, so it can be called.
Here we can continue to think about the first case, when the component's own build process calls setState and causes it to rebuild, will it also be a dead loop? Let's continue to lookmarkNeedsBuild()
The logic behind the second half of the code, if the assertion is successful:
void markNeedsBuild() { // ... // The first half is assertion. if (dirty) { return; } _dirty = true; owner!.scheduleBuildFor(this); }
Here you can see the component during the build processmarkNeedsBuild()
It will make the component dirty state. At this time, when you directly call setState in the build and find that it is already dirty state, it will return directly, and will not schedule and rebuild, so there is no problem.
Summarize
Through the above analysis, we know how Flutter determines whether it is legal to call setState directly during the build process. Of course, when we write code, we will not call setState directly in build(). Understanding the above process will help us troubleshoot problems and learn the operating principles of Flutter.
The above is a detailed explanation of whether Flutter setState can be called directly in the build. For more information about Flutter setState calling build, please follow my other related articles!