본문 바로가기

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()),
          );
        }
      });
    });
  }