-
[Riverpod] Unhandled Exception: Tried to modify a provider while the widget tree was building.Programing/Flutter 2024. 6. 9. 21:52반응형
어떤 화면에 로딩 인디케이터(loading indicator - 작업 완료될 때 까지 뱅글뱅글)를 넣어놨는데,
작업이 완료되기 전에 해당 화면에서 빠져나갈 경우, 다시 그 화면으로 들어갔을 때
무한정 뱅글뱅글 돌아가는 에러가 있음을 확인했습니다.
그래서 그 화면에 들어가자마자 indicator.status = false로 설정해두면 되겠지... 하면서 다음과 같이 코드를 작성했습니다.
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:projects/common/w_loading_indicator.dart'; class ChatScreen extends ConsumerStatefulWidget { const ChatScreen({super.key}); @override ChatScreenState createState() => ChatScreenState(); } class ChatScreenState extends ConsumerState<ChatScreen> { @override void initState() { super.initState(); ref.read(loadingProvider.notifier).state = false; } // 기타 코드 생략
그런데 사진처럼 무섭게 생긴 에러가 발생했습니다.
E/flutter ( 5343): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Tried to modify a provider while the widget tree was building. E/flutter ( 5343): If you are encountering this error, chances are you tried to modify a provider E/flutter ( 5343): in a widget life-cycle, such as but not limited to: E/flutter ( 5343): - build E/flutter ( 5343): - initState E/flutter ( 5343): - dispose E/flutter ( 5343): - didUpdateWidget E/flutter ( 5343): - didChangeDependencies E/flutter ( 5343): E/flutter ( 5343): Modifying a provider inside those life-cycles is not allowed, as it could E/flutter ( 5343): lead to an inconsistent UI state. For example, two widgets could listen to the E/flutter ( 5343): same provider, but incorrectly receive different states. E/flutter ( 5343): E/flutter ( 5343): E/flutter ( 5343): To fix this problem, you have one of two solutions: E/flutter ( 5343): - (preferred) Move the logic for modifying your provider outside of a widget E/flutter ( 5343): life-cycle. For example, maybe you could update your provider inside a button's E/flutter ( 5343): onPressed instead. E/flutter ( 5343): E/flutter ( 5343): - Delay your modification, such as by encapsulating the modification E/flutter ( 5343): in a `Future(() {...})`. E/flutter ( 5343): This will perform your update after the widget tree is done building. E/flutter ( 5343): #0 _UncontrolledProviderScopeElement._debugCanModifyProviders (package:flutter_riverpod/src/framework.dart:349:7) E/flutter ( 5343): #1 ProviderElementBase._notifyListeners.<anonymous closure> (package:riverpod/src/framework/element.dart:471:34) E/flutter ( 5343): #2 ProviderElementBase._notifyListeners (package:riverpod/src/framework/element.dart:473:8) // 이후 생략
아직 위젯트리가 다 생성되지 않았는데 provider(Riverpod)가 작동해서 충돌이 일어났기 때문에 발생한 문제인 것 같습니다.
해결
화면 렌더링을 마친 이후에 status를 변경해주도록 합시다.
class ChatScreen extends ConsumerStatefulWidget { const ChatScreen({super.key}); @override ChatScreenState createState() => ChatScreenState(); } class ChatScreenState extends ConsumerState<ChatScreen> { @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { // 화면 렌더링 완료 후 작업 진행 ref.read(loadingProvider.notifier).state = false; }); } // 기타 코드 생략
상세 설명
왜 addPostFrameCallback이 필요한가?
- 상태 불일치 방지:
- 초기화 과정 중에 Flutter가 여러 번 프레임을 갱신할 수도 있습니다.
만약 프레임 중간에 상태를 변경하면 불필요한 재렌더링이나 상태 불일치가 발생할 수 있습니다.
때문에 initState에서 직접 Provider의 status를 설정하지 않고 프레임 렌더링이 끝난 후에 설정합니다.
- 초기화 과정 중에 Flutter가 여러 번 프레임을 갱신할 수도 있습니다.
- 초기화 로직과 렌더링 분리:
- 특정 작업이 렌더링 후에 수행되어야 하는 경우, addPostFrameCallback을 사용하여 이를 보장합니다.
이는 초기화 로직과 렌더링 로직을 명확히 분리할 수 있게 해줍니다.
(위의 제 사례에서는 그런 경우는 아닙니다.)
- 특정 작업이 렌더링 후에 수행되어야 하는 경우, addPostFrameCallback을 사용하여 이를 보장합니다.
- 안전한 상태 업데이트:
- 위젯이 처음 빌드될 때 상태를 안전하게 업데이트할 수 있게 합니다.
빌드 과정이 완료된 후에 상태를 업데이트함으로써 무한 루프나 성능 문제를 피할 수 있습니다.
- 위젯이 처음 빌드될 때 상태를 안전하게 업데이트할 수 있게 합니다.
참고한 자료: https://flutter.salon/error_warning/modify_provider_while_widget_building/
반응형'Programing > Flutter' 카테고리의 다른 글
- 상태 불일치 방지: