본문 바로가기
에러 해결방법

[에러] The following assertion was thrown building: Tried to modify a provider while the widget tree was building.

by 개발짜 2025. 1. 6.

문제 상황

List<Feed> feeds 라는 배열을 상태(state) 로 두고 PageView 위젯에서 feeds 를 순서대로 보여준다. feeds 는 디폴트로 10개의 Feed 객체를 담고 있는데, 마지막 Feed 일 경우 새로운 Feed 객체를 배열에 추가하여 다음 페이지로 넘겨도 PageView 가 계속 보였으면 한다. 하지만 마지막 페이지를 넘기면 오류 화면이 뜨고 핫리로드를 해야 정상적인 화면이 보여진다.

 

문제

 

요약하자면 위젯 트리가 빌드되는 동안 provider 가 변경되어 오류가 발생한 것이다.

════════ Exception caught by widgets library ═══════════════════════════════════ 
The following assertion was thrown building: 
Tried to modify a provider while the widget tree was building. 
If you are encountering this error, chances are you tried to modify a provider 
in a widget life-cycle, such as but not limited to: 

- build 
- initState 
- dispose 
- didUpdateWidget 
- didChangeDependencies 

Modifying a provider inside those life-cycles is not allowed, as it could
lead to an inconsistent UI state. For example, two widgets could listen to the
same provider, but incorrectly receive different states.

 

코드는 다음과 같다.

class HomePage extends ConsumerStatefulWidget {
  @override
  ConsumerState<HomePage> createState() => _HomePageState();
}

class _HomePageState extends ConsumerState<HomePage> {
  final _pageController = PageController();

  @override
  void dispose() {
    _pageController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final state = ref.watch(homeViewModelProvider)!;
    final vm = ref.watch(homeViewModelProvider.notifier);
    return Scaffold(
      body: PageView.builder(
        scrollDirection: Axis.vertical,
        controller: _pageController,
        itemCount: state.feeds.length,
        itemBuilder: (context, index) {
          // 마지막 페이지일 때
          if (index + 1 == state.feeds.length) {
            // feed 정보 업데이트
            vm.moreFetch();
          }

          return Stack(
            children: [
              Container(
                height: double.infinity,
                width: double.infinity,
                child: Image.network(
                  state.feeds[index].imageUrl,
                  fit: BoxFit.fill,
                ),
              ),
              Center(
                child: Text(
                  state.feeds[index].id,
                  style: TextStyle(fontSize: 100),
                ),
              ),
            ],
          );
        },
      ),
    );
  }
}

 

HomeViewModel.dart

class HomeViewModel extends Notifier<HomeState?> {
  int count = 7;
  @override
  HomeState? build() {
    fetch();
    return HomeState(feeds: [
      Feed(id: '1', imageUrl: 'https://picsum.photos/200/300'),
      Feed(id: '2', imageUrl: 'https://picsum.photos/200/300'),
      Feed(id: '3', imageUrl: 'https://picsum.photos/200/300'),
      Feed(id: '4', imageUrl: 'https://picsum.photos/200/300'),
      Feed(id: '5', imageUrl: 'https://picsum.photos/200/300'),
      Feed(id: '6', imageUrl: 'https://picsum.photos/200/300'),
      Feed(id: '7', imageUrl: 'https://picsum.photos/200/300'),
    ]);
  }

  void moreFetch() {
    List<Feed> moreFeed = [
      ...state!.feeds,
      Feed(id: '${count++}', imageUrl: 'https://picsum.photos/200/300')
    ];
    state = HomeState(feeds: moreFeed);
  }
}

 

해결 방법

플러터는 친절하게도 해결 방법을 알려준다.

To fix this problem, you have one of two solutions:
- (preferred) Move the logic for modifying your provider outside of a widget 
  life-cycle. For example, maybe you could update your provider inside a button's 
  onPressed instead.

- Delay your modification, such as by encapsulating the modification 
  in a `Future(() {...})`. 
  This will perform your update after the widget tree is done building.

1. provider 수정하는 로직을 위젯 builder 외에서 하는 방법

2. 변경을 delayed 하여 위젯이 다 빌드 된 후 업데이트 하는 방법

 

나는 1번 방법을 채택해서 해결했다.

 

class HomePage extends ConsumerStatefulWidget {
  @override
  ConsumerState<HomePage> createState() => _HomePageState();
}

class _HomePageState extends ConsumerState<HomePage> {
  final _pageController = PageController();

  @override
  void dispose() {
    _pageController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final state = ref.watch(homeViewModelProvider)!;
    final vm = ref.watch(homeViewModelProvider.notifier);
    return Scaffold(
      body: PageView.builder(
        scrollDirection: Axis.vertical,
        controller: _pageController,
        // 페이지가 변경될 때 fetch 하면 됨
        // onPageChanged 는 페이지의 build 전에 호출되므로 오류를 해결할 수 있다!
        onPageChanged: (page) {
          // 마지막 페이지일 경우 추가 피드를 가져온다
          if (page + 1 == state.feeds.length) {
            vm.moreFetch();
          }
        },
        itemBuilder: (context, index) {
          return Stack(
            children: [
              Container(
                height: double.infinity,
                width: double.infinity,
                child: Image.network(
                  state.feeds[index].imageUrl,
                  fit: BoxFit.fill,
                ),
              ),
              Center(
                child: Text(
                  state.feeds[index].id,
                  style: TextStyle(fontSize: 100),
                ),
              ),
            ],
          );
        },
      ),
    );
  }
}

댓글