skip to Main Content

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?

2

Answers


  1. 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.

    Login or Signup to reply.
  2. Here is what you could do if you want to do an HTTP request with an Asynchronize, there are two ways if you do not have a state management like Bloc, Provider, etc.

    1. **Initstate.**
    
    
      class YourWidget extends StatefulWidget {
          @override
          _YourWidgetState createState() => _YourWidgetState();
        }
        
        class _YourWidgetState extends State<YourWidget> {
          bool isLoading = true;
          bool hasError = false;
        
          @override
          void initState() {
            super.initState();
            readRequiredData();
          }
        
          Future<void> readRequiredData() async {
            try {
           
              await Future.delayed(Duration(seconds: 2));
        
            } catch (error) {
              print('Error: $error');
              setState(() {
                hasError = true;
              });
            } finally {
              setState(() {
                isLoading = false;
              });
            }
          }
        
          @override
          Widget build(BuildContext context) {
            if (isLoading) {
              return const CircularProgressIndicator();
            } else if (hasError) {
              return const Text('Error occurred during data fetching');
            } else {
              return YourActualWidget();
            }
          }
        }
        
        class YourActualWidget extends StatelessWidget {
          @override
          Widget build(BuildContext context) {
            return Container(
              child: Text('Widget built with fetched data'),
            );
          }
        }
    
    2. **Future Builder**
    
        class YourWidget extends StatefulWidget {
          @override
          _YourWidgetState createState() => _YourWidgetState();
        }
    
    class _YourWidgetState extends State<YourWidget> {
      bool isLoading = true;
    
      @override
      void initState() {
        super.initState();
        readRequiredData();
      }
    
      Future<void> readRequiredData() async {
        try {
         
          await Future.delayed(Duration(seconds: 2));
    
        } catch (error) {
          print('Error: $error');
        } finally {
      
          setState(() {
            isLoading = false;
          });
        }
      }
    
      @override
      Widget build(BuildContext context) {
        if (isLoading) {
          return CircularProgressIndicator();
        } else {
          return YourActualWidget();
        }
      }
    }
    
    class YourActualWidget extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Container(
          child: Text('Widget built with fetched data'),
        );
      }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search