London Tran
London Tran

Reputation: 272

How to call method only after async method finishes

Background I have a class PriceScreen that contains an asynchronous method getData(). This PriceScreen class makes a call to class Graph using data from graphValues returned from getData().

Problem The call to Graph Graph(closingTimesAndPrices: graphValues)

is running before getData is completed, resulting in the passed graphValues becoming Null in the call above. How can I change my code so that the call to Graph waits to be called until getData is finished processing?

Note: I have added "<<<----------" to parts of the code that I believe are important to answering my question.

class PriceScreen extends StatefulWidget {
  @override
  PriceScreenState createState() => PriceScreenState();
}

class PriceScreenState extends State < PriceScreen > {
  String selectedCurrency = 'USD';
  String selectedGraphType = "1M";
  Map < String,
  String > coinValues = {};
  Map < String,
  double > graphValues = {};

  bool isWaiting = false;

  void getData() async {<<< -- -- -- -- -- -- -- -- -- -
    isWaiting = true;
    try {
      Map coinData = await CoinData().getCoinData(selectedCurrency);
      Map graphData = await GraphData().getGraphData( <<< -- -- -- -- -
        selectedCurrency: selectedCurrency,
        selectedGraphType: selectedGraphType);
      isWaiting = false;
      setState(() {
        coinValues = coinData;
        graphValues = graphData; <<< -- -- -- -- -- -
      });
    } catch (e) {
      print(e);
    }
  }

  @override
  void initState() {
    super.initState();
    getData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Crypto Watcher'),
      ),
      body: ModalProgressHUD(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: < Widget > [
            Graph(closingTimesAndPrices: graphValues), <<< -- -- --
          ]),
        inAsyncCall: isWaiting,
        progressIndicator: CircularProgressIndicator(
          backgroundColor: Colors.orange,
        ),
      ),
    );
  }
}

Upvotes: 0

Views: 221

Answers (3)

London Tran
London Tran

Reputation: 272

Thank you to those who have responded to my question and for recommending the use of StreamBuilder. I used StreamBuilder to overcome the issue from my Original Post.

I needed to add three parts to my code:

 1) Future futureData;

 2) futureData = getData();

 3) FutureBuilder(
                future: futureData,
                builder: (context, snapshot) {
                  if (graphValues.isEmpty) {
                    return new Container();
                  } else
                  return Graph(closingTimesAndPrices: graphValues);
                })

I used futureData for the value of future: in the FutureBuilder to signal that I am waiting on futureData (getData();) to fully complete before I return anything. In my Original Post, I mentioned that graphValues were returning null before getData() was finished processing. To remedy this, I check if graphValues is empty with

                      if (graphValues.isEmpty) {
                        return new Container();
                      } else
                      return Graph(closingTimesAndPrices: graphValues);

If graphValues is empty, I return new Container(); which doesn't show anything to the user. When graphValues is not empty, I return the call to Graph with Graph(closingTimesAndPrices: graphValues);. Some may recommend that I include a CircularProgressIndicator instead of new Container(); when graphValues is empty, but I already have a CircularProgressIndicator as indicated by body: ModalProgressHUD( and

inAsyncCall: isWaiting,
        progressIndicator: CircularProgressIndicator(
          backgroundColor: Colors.orange, 

Here are some resources that helped me learn StreamBuilder and helped me solve the problem:

https://www.youtube.com/watch?v=LYN46233cws

https://stackoverflow.com/a/63450354/3932449

The additions to my code are denoted with three asterisks (***) below.

Full code:

class PriceScreen extends StatefulWidget {
  @override
  PriceScreenState createState() => PriceScreenState();
}

class PriceScreenState extends State < PriceScreen > {
  String selectedCurrency = 'USD';
  String selectedGraphType = "1M";
  Map < String,String > coinValues = {};
  Map < String,double > graphValues = {};
 ***Future futureData;

  bool isWaiting = false;

  getData() async {
    isWaiting = true;
    try {
      Map coinData = await CoinData().getCoinData(selectedCurrency);
      Map graphData = await GraphData().getGraphData( 
        selectedCurrency: selectedCurrency,
        selectedGraphType: selectedGraphType);
      isWaiting = false;
      setState(() {
        coinValues = coinData;
        graphValues = graphData; 
      });
    } catch (e) {
      print(e);
    }
  }

  @override
  void initState() {
    super.initState();
 ***futureData = getData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Crypto Watcher'),
      ),
      body: ModalProgressHUD(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          crossAxisAlignment: CrossAxisAlignment.stretch,
       ***child: FutureBuilder(
                future: futureData,
                builder: (context, snapshot) {
                  if (graphValues.isEmpty) {
                    return new Container();
                  } else
                    print(graphValues);
                  return Graph(closingTimesAndPrices: graphValues);
                }), 
          ),
        inAsyncCall: isWaiting,
        progressIndicator: CircularProgressIndicator(
          backgroundColor: Colors.orange,
        ),
      ),
    );
  }
}

Upvotes: 0

Md Golam Rahman Tushar
Md Golam Rahman Tushar

Reputation: 2375

Well, the best practise is not to defer executing the build method. What you should do is to show a progress bar while the graphValues are being fetched.

Now, you can achieve this in multiple ways. Using bloc, streambuilder etc are some best ways. But for the simplest solution you can use the below code:

@override
Widget build(BuildContext context) {
      if(grapheValues.isEmpty) {
        return Center(
          child: CircularProgressIndicator(),
        );
      } else {
          Graph(closingTimesAndPrices: graphValues), <<<-------------------
      }
}

Hopefully it will help. Let me know if you need any further help. Happy coding!

UPDATE Set isWaiting to true inside the initState method. And then inside the setState of getData() method set isWaiting to true. This should work.

Upvotes: 1

Dung Ngo
Dung Ngo

Reputation: 1452

I usually pair the next await method in a then(). Something like this:

Map coinData = await CoinData().getCoinData(selectedCurrency);
await GraphData().getGraphData(
  selectedCurrency: selectedCurrency,
  selectedGraphType: selectedGraphType).then((graphData ) {
  setState(() {
    coinValues = coinData;
    graphValues = graphData;
  });
});

Upvotes: 0

Related Questions