Search code examples
flutterflutter-listviewflutter-stateflutter-future

Correct way to load ListView data source initially


I have a stateful widget whose state builds a ListView. The ListView gets its data from an http API. I am using a Future<void> method called getData to retrieve this data and populate a List<> with it before calling setState.

My question is where should I call getData when this screen first launches? If I call it in initState(), I get the following error in the debug console:

[VERBOSE-2:ui_dart_state.cc(198)] Unhandled Exception: dependOnInheritedWidgetOfExactType<_InheritedTheme>() or dependOnInheritedElement() was called before _EventListState.initState() completed.

If I wrap the call to getData in a delayed Future, I do not see the error. Here's my code:

class _EventListState extends State<EventList> {

  Future<void> getData() async {
    events = [];
    events = await Network.getUsers(context);
    setState(() {});
  }

  List<Event> events = [];

  @override
  initState() {
    super.initState();
    getData(); // this cause the error
    // Future.delayed(Duration(seconds: 1), getData); // this works
  }

  @override
  build(context) {
    return PlatformScaffold(
      iosContentPadding: true,
      body: ListView.builder(
        padding: const EdgeInsets.all(10),
        physics: const AlwaysScrollableScrollPhysics(),
        itemCount: events.length,
        itemBuilder: (context, index) => Text(events[index].summary),
      ),
    );
  }
}

Forcing a delay to retrieve the data does not feel right, so is there a better way?


Solution

  • Use FutureBuilder.

    List<Event> events = [];
    
      @override
      Widget build(BuildContext context) {
        return FutureBuilder(
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.done) {
              return PlatformScaffold(
                iosContentPadding: true,
                body: ListView.builder(
                  padding: const EdgeInsets.all(10),
                  physics: const AlwaysScrollableScrollPhysics(),
                  itemCount: events.length,
                  itemBuilder: (context, index) => Text(events[index].summary),
                ),
              );
            } else if (snapshot.hasError) {
              return Center(child: Text('Error: ${snapshot.error}'));
            } else {
              return Center(child: Text('Please wait its loading...'));
            }
          },
          future: getData(),
        );
      }
    
      Future<void> getData() async {
        events = [];
        events = await Network.getUsers(context);
      }