Search code examples
flutterdartasynchronous

How make sure that an asynchronous data fetching method has completed before build method is called?


I am using the model view controller pattern and I have a view which uses data from the database to build the view's widgets. The problem is that fetching of the data is asynchronous, so I use the async and await keywords in a method I call readRequiredData() to make sure the data fetching is completed before any code after the await keyword gets executed.

I first tried putting the readRequiredData() method into the constructor of the view's widget to make sure it is executed first, the readRequiredData() method gets called alright but when the await keyword is reached the readRequiredData() method gets exited and the widget's build method gets called, this causes an error as the page or view's widget does not have the required data to build the widgets.

I then tried putting it into the initState() method of the widget's state class again, it does not await for the data fetching to be finished, the code after the await keyword is not executed immediately alright, but the readRequiredData() is exited again and the build method is called. How do I make sure that the data from the database has been fully collected in the form I need before widget building begins?


Solution

  • You can't make the build method waits for a future to complete, but instead you should show an alternative widget when the future has not completed yet. You can do this with FutureBuilder. Here's an example:

    class FutureBuilderExample extends StatefulWidget {
      const FutureBuilderExample({super.key});
    
      @override
      State<FutureBuilderExample> createState() => _FutureBuilderExampleState();
    }
    
    class _FutureBuilderExampleState extends State<FutureBuilderExample> {
      final Future<String> _calculation = Future<String>.delayed(
        const Duration(seconds: 2),
        () => 'Data Loaded',
      );
    
      @override
      Widget build(BuildContext context) {
        return FutureBuilder<String>(
          future: _calculation, // a previously-obtained Future<String> or null
          builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
            if (snapshot.hasData) {
              final data = snapshot.data;
              return Text('This is the widget to show when the future completes. Result: $data');
            } else if (snapshot.hasError) {
              return Text('This is the widget to show when the error occurs.');
            } else {
              // This is the widget to show when the future is still pending.
              return CircularProgressIndicator();
            }
          },
        );
      }
    }
    

    If you prefer a video instructions: https://youtu.be/zEdw_1B7JHY


    Alternatively, you can use Riverpod and use its FutureProvider which wraps the future computation result into an AsyncData that handles the loading, error, and data state nicely.