I want to load a list of events and display a loading indicator while fetching data.
I'm trying Provider pattern (actually refactoring an existing application).
So the event list display is conditional according to a status managed in the provider.
Problem is when I make a call to notifyListeners()
too quickly, I get this exception :
════════ Exception caught by foundation library ════════
The following assertion was thrown while dispatching notifications for EventProvider:
setState() or markNeedsBuild() called during build.
...
The EventProvider sending notification was: Instance of 'EventProvider'
════════════════════════════════════════
Waiting for some milliseconds before calling notifyListeners()
solve the problem (see commented line in the provider class below).
This is a simple example based on my code (hope not over simplified) :
main function :
Future<void> main() async {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => LoginProvider()),
ChangeNotifierProvider(create: (_) => EventProvider()),
],
child: MyApp(),
),
);
}
root widget :
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();
},
);
}
}
home page :
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(...)
)
}
}
event provider :
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();
}
}
Can someone explain what happens?
NotifyListeners()
to callsetState()
while widget is building (except from being called manually). As I have only stateless widgets in my whole app, and do not call anysetState()
, I did not understand. My first though was to fetch events in theEventProvider
constructor, but I need the user email from theLoginProvider
and have not managed to get it to work even passing constructor argument on routing (gave me exception(inheritFromWidgetOfExactType(_ModalScopeStatus) or inheritFromElement() was called
. – Pug