SoFunction
Updated on 2025-03-06

Detailed explanation of whether Flutter development setState can be called directly in the build

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<TestPage> {
  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<DiagnosticsNode> information = <DiagnosticsNode>[
          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._debugCurrentBuildTargetIt 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()thisIt is the component that calls setState() to cause rebuild.targetIt is the component currently being built. Among themwhileThe 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!