빌드하는 동안 플러터 공급자 setState() 또는 MarkNeedsBuild() 호출됨
빌드하는 동안 플러터 공급자 setState() 또는 MarkNeedsBuild() 호출됨
데이터를 가져오는 동안 이벤트 목록을 로드하고 로드 표시기를 표시하려고 합니다.
패턴을 시도하고 있습니다(실제로 기존 응용 프로그램을 리팩토링).
따라서 이벤트 목록 표시는 공급자에서 관리되는 상태에 따라 조건부입니다.
문제는 너무 빨리 전화를 걸면 다음과 같은 예외가 발생한다는 것입니다:
════════ Foundation 라이브러리에서 예외가 발견되었습니다
이벤트 공급자에 대한 알림을 발송하는 동안 다음 주장이 던져졌습니다:
빌드하는 동안 setState() 또는 MarkNeedsBuild()가 호출되었습니다.
...
이벤트 공급자가 알림을 보내는 방법: 'EventProvider
════════════════════════════════════════
를 호출하기 전에 몇 밀리초를 기다리면 문제가 해결됩니다(아래 공급자 클래스의 주석 줄 참조).
다음은 내 코드를 기반으로 한 간단한 예이다(너무 단순하지 않기를 바란다):
:
Future<void> main() async {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => LoginProvider()),
ChangeNotifierProvider(create: (_) => EventProvider()),
],
child: MyApp(),
),
);
}
:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final LoginProvider _loginProvider = Provider.of<LoginProvider>(context, listen: true);
final EventProvider _eventProvider = Provider.of<EventProvider>(context, listen: false);
// load user events when user is logged
if (_loginProvider.loggedUser != null) {
_eventProvider.fetchEvents(_loginProvider.loggedUser.email);
}
return MaterialApp(
home: switch (_loginProvider.status) {
case AuthStatus.Unauthenticated:
return MyLoginPage();
case AuthStatus.Authenticated:
return MyHomePage();
},
);
}
}
:
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final EventProvider _eventProvider = Provider.of<EventProvider>(context, listen: true);
return Scaffold(
body: _eventProvider.status == EventLoadingStatus.Loading ? CircularProgressIndicator() : ListView.builder(...)
)
}
}
:
enum EventLoadingStatus { NotLoaded, Loading, Loaded }
class EventProvider extends ChangeNotifier {
final List<Event> _events = [];
EventLoadingStatus _eventLoadingStatus = EventLoadingStatus.NotLoaded;
EventLoadingStatus get status => _eventLoadingStatus;
Future<void> fetchEvents(String email) async {
//await Future.delayed(const Duration(milliseconds: 100), (){});
_eventLoadingStatus = EventLoadingStatus.Loading;
notifyListeners();
List<Event> events = await EventService().getEventsByUser(email);
_events.clear();
_events.addAll(events);
_eventLoadingStatus = EventLoadingStatus.Loaded;
notifyListeners();
}
}
누가 무슨 일이 일어나는지 설명해줄 수 있나요?
문제는 당신이 한 기능으로 두 번 전화를 걸었다는 것이다. 이해해요, 당신은 주를 바꾸고 싶어해요. 그러나 앱이 로드될 때 이를 알리는 것은 이벤트 제공자의 책임입니다. 당신이 해야 할 일은 만약 그것이 로딩되지 않았다면, 로딩 중이라고 가정하고 그냥 a를 넣는 것이다. 같은 기능으로 두 번 전화하지 마라. 그것은 당신에게 아무런 도움이 되지 않는다.
정말로 하고 싶다면, 다음을 시도해 보세요:
Future<void> fetchEvents(String email) async {
markAsLoading();
List<Event> events = await EventService().getEventsByUser(email);
_events.clear();
_events.addAll(events);
_eventLoadingStatus = EventLoadingStatus.Loaded;
notifyListeners();
}
void markAsLoading() {
_eventLoadingStatus = EventLoadingStatus.Loading;
notifyListeners();
}
루트 위젯에 대해 빌드 코드 내에서 호출하고 있습니다. 내에서 를 호출하면 이벤트 공급자를 수신하는 위젯이 호출됩니다. 이 문제는 위젯이 재구성 중인 경우 위젯을 호출할 수 없기 때문입니다.
이 시점에서 여러분은 "그러나 메소드가 로 표시되어 있으므로 나중에 비동기적으로 실행되어야 한다"고 생각할 수 있습니다. 그리고 그것에 대한 대답은 "예스 앤 노"이다. Dart에서 작동하는 방식은 메서드를 호출할 때 Dart가 가능한 한 많은 코드를 동기적으로 실행하려고 시도하는 것입니다. 간단히 말해서, 당신의 메소드에서 에 앞서 나오는 모든 코드가 정상적인 동기 코드로 실행된다는 것을 의미한다. 귀사의 방법을 살펴보면 다음과 같습니다:
Future<void> fetchEvents(String email) async {
//await Future.delayed(const Duration(milliseconds: 100), (){});
_eventLoadingStatus = EventLoadingStatus.Loading;
notifyListeners();
List<Event> events = await EventService().getEventsByUser(email);
_events.clear();
_events.addAll(events);
_eventLoadingStatus = EventLoadingStatus.Loaded;
notifyListeners();
}
에 대한 호출에서 첫 번째가 발생한다는 것을 알 수 있습니다. 그 전에 하나가 있어서, 그것은 동시에 호출될 것이다. 즉, 위젯의 빌드 메서드에서 이 메서드를 호출하는 것은 앞서 말한 것처럼 빌드 메서드 자체를 호출하는 것이 금지됩니다.
호출을 에 추가할 때 이 기능이 작동하는 이유는 메소드의 맨 위에 가 있기 때문에 메소드 아래의 모든 것이 비동기적으로 실행되기 때문입니다. 일단 실행이 가 호출하는 코드 부분에 도달하면, 플러터는 더 이상 위젯을 재구성하는 상태가 아니므로, 그 시점에서 해당 메서드를 호출하는 것이 안전합니다.
대신 메소드에서 호출할 수 있지만 위젯이 초기화되기 전에는 호출할 수 없습니다.
그렇다면, 해결책은 이것이다. 이벤트 공급자가 로드 중임을 모든 위젯에 알리는 대신 이벤트 공급자가 생성될 때 기본적으로 로드되도록 합니다. (만든 후에 모든 이벤트를 로드하기 때문에 처음 생성할 때 로드하지 않아도 됩니다.) 이렇게 하면 메소드를 시작할 때 공급자를 로드로 표시할 필요가 없어지므로 다음을 호출할 필요가 없습니다:
EventLoadingStatus _eventLoadingStatus = EventLoadingStatus.Loading;
// or
late EventLoadingStatus _eventLoadingStatus;
@override
void initState() {
super.initState();
_eventLoadingStatus = EventLoadingStatus.Loading;
}
...
Future<void> fetchEvents(String email) async {
List<Event> events = await EventService().getEventsByUser(email);
_events.clear();
_events.addAll(events);
_eventLoadingStatus = EventLoadingStatus.Loaded;
notifyListeners();
}
루트 위젯을 위해 코드 내에서 아피스를 호출하고 있습니다. 아피스 내에서, 당신은 통지를 요청합니다수신기 - 이벤트 공급자를 수신하는 위젯에서 setState를 호출합니다. 따라서 먼저 코드에서 setState를 제거하고 init 상태에서 호출할 때 Future use를 사용해야 합니다
@override
void initState() {
super.initState();
Future.microtask(() => context.read<SellCarProvider>().getBrandService(context));
}