본문 바로가기

Flutter

setState() or markNeedsBuild() called during build 에러 원인과 해결

`setState() or markNeedsBuild() called during build` 에러의 원인

정진규 Software Engineer 노션에 따르면 마치 Null Pointer Exception과 같은 광범위한 에러이다. 따라서 에러가 발생하는 시점에 관한 코드를 자세히 살펴보는 것이 좋다. 에러의 원인은 말 그대로 UI를 이미 빌드하고 있는데 해당 UI가 구독하고 있는 state에 변경이 일어나서 또 변경하게 생겼다는 것이다. 

 

 

`setState() or markNeedsBuild() called during build` 에러 원인 분석

나의 경우에는 TestPage에 진입을 시도할 때마다 해당 에러가 발생하였다. 따라서 TestPage를 살펴보았다. TestPage는 TestState의 Consumer로서 TestState에 변경이 일어나면 rebuild를 하게 되어있다. 그런데 `build()` 부분에는 testState에 변경을 가하는 부분을 찾아볼 수 없었고, state를 읽어오는 부분 밖에 없었다.

 

진입을 시도할 때마다 해당 에러가 발생하는 점 때문에 TestPage `initState()`를 주목하였다. 그랬더니 아래 코드와 같이 UI를 빌드하는 코드에서 ChangeNotifier인 TestState class를 건드리고 있었다. (`testState.startNewTest()` 부분)

@override 
void initState() {
    super.initState();
    final testState = context.read<TestState>();
    audioTimer = testState.audioTimer;
    testState.startNewTest();
    // 퀴즈 종료 시 이동할 화면 설정
    testState.setOnTestEnd(() {
      if (mounted) {
        Navigator.pushReplacement(
          context,
          MaterialPageRoute(builder: (context) => const EndingPage()),
        );
      }
    });
}

 

즉, initState()가 실행된 다음에 build()가 실행되는데, initState()에 들어있는 testState.startNew()가 내부적으로 notifyListener()를 트리거하게 되고, build()가 UI를 그리고 있는 와중에 이 변경을 감지하면 이를 막기 위해 flutter가 `setState() or markNeedsBuild() called during build` 에러를 발생시키는 것이다. 

 

 

`setState() or markNeedsBuild() called during build` 에러 해결

build()가 모두 완료된 후에 상태변경을 보장할 수 있도록 `addPostFrameCallback`를 사용한다. 

@override
  void initState() {
    super.initState();

    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      final testState = context.read<TestState>();
      audioTimer = testState.audioTimer;
      testState.startNewTest();
      // 퀴즈 종료 시 이동할 화면 설정
      testState.setOnTestEnd(() {
        if (mounted) {
          Navigator.pushReplacement(
            context,
            MaterialPageRoute(builder: (context) => const EndingPage()),
          );
        }
      });
    });
  }