最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

flutter - When do I call setState to re-build the widget tree with values obtained from an asynchronous call? - Stack Overflow

programmeradmin3浏览0评论

I have a UI that uses http to get data from CoinApi.io. This async call populates a map that is in turn used to update the SliverGrid. This is based on a currency selection from a Cupertino app. What I find is that after the currency is selected, the price listing is always a step behind instead of matching/syncing. Here is the simple code I have thus far.

/// A widget that displays a dashboard for Bitcoin ticker information.
class BitCoinTickerDashBoard extends StatefulWidget {
  const BitCoinTickerDashBoard({super.key});

  @override
  State<BitCoinTickerDashBoard> createState() => _BitCoinTickerDashBoardState();
}

class _BitCoinTickerDashBoardState extends State<BitCoinTickerDashBoard> {
  bool endOfScroll = false;

  late Map<String, String> cryptoItemPrice = {
    'BTC': '0',
    'ETH': '0',
    'LTC': '0',
    'ADA': '0',
    'XRP': '0',
    'DOGE': '0',
    'XLM': '0',
  };

  String currency = 'USD';

  @override
  void initState() {
    currency = 'USD';
    getCryptoPrices();
    setState(() {});
    super.initState();
  }

  Text getBalance() {
    return Text(
      'USD: \$7,985,000.00',
      style: TextStyle(
          color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold),
    );
  }

  void getCryptoPrices() {
    // for all the supported cryptos construct an appropriate url
    // and using NetworkHelper, fetch the current price in the
    // current selected currency

    cryptoList.forEach(
      (crypto, _) async {
        // form the full list
        String url = '$kCoinIoApiUrl$crypto/$currency?apikey=$kCoinIoApiKeyER';

        NetworkHelper networkHelper = NetworkHelper(url: url);
        // issue the request
        var price = await networkHelper.getData();

        if (price != null) {
          double rate = price['rate'];

          cryptoItemPrice[crypto] = rate.toStringAsFixed(8);
        } else {
          cryptoItemPrice[crypto] = '-1';
        }
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      body: SafeArea(
        child: CustomScrollView(
          slivers: [           
            SliverAppBar(
              backgroundColor: Colors.white,
              expandedHeight: 200,
              toolbarHeight: 200,
              pinned: true,
              flexibleSpace: FlexibleSpaceBar(
                background: Padding(
                  padding: EdgeInsets.symmetric(horizontal: 20),
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Card(
                        elevation: 15,
                        shape: RoundedRectangleBorder(
                          borderRadius: BorderRadius.circular(10),
                        ),
                        color: Colors.deepPurple, // kPickerColor,
                        child: Padding(
                          padding: const EdgeInsets.all(20.0),
                          child: Column(
                            children: [
                              ListTile(
                                title: Text(
                                  'Your Balance',
                                  style: TextStyle(
                                    color: Colors.white,
                                    fontSize: 20,
                                    //fontWeight: FontWeight.bold,
                                  ),
                                ),
                                subtitle: getBalance(),
                                trailing: SizedBox(
                                  width: 100,
                                  child: Column(
                                    crossAxisAlignment:
                                        CrossAxisAlignment.center,
                                    children: [
                                      Expanded(
                                        flex: 3,
                                        child: NotificationListener<
                                            ScrollNotification>(
                                          onNotification: (scrollNotification) {
                                            if (scrollNotification
                                                is ScrollEndNotification) {
                                              endOfScroll = true;
                                            }
                                            return true;
                                          },
                                          child: CupertinoPicker(
                                            scrollController:
                                                FixedExtentScrollController(
                                                    initialItem: 19),
                                            backgroundColor:
                                                Colors.black87, //kPickerColor,
                                            itemExtent: 20,
                                            onSelectedItemChanged:
                                                (onSelectedItemChanged) async {
                                              currency = currenciesList[
                                                  onSelectedItemChanged];
                                              if (endOfScroll) {
                                                getCryptoPrices();
                                                setState(() {});
                                              }
                                            },
                                            children: getCurrency(),
                                          ),
                                        ),
                                      ),
                                      Expanded(child: SizedBox(height: 10)),
                                      Expanded(
                                        flex: 2,
                                        child: Text(
                                          'Currency',
                                          style: TextStyle(
                                            color: Colors.white,
                                            fontSize: 15,
                                          ),
                                        ),
                                      ),
                                    ],
                                  ),
                                ),
                              ),
                            ],
                          ),
                        ),
                      ),                      
                    ],
                  ),
                ),
              ),
            ),
            SliverGrid(
              delegate: SliverChildBuilderDelegate(
                (context, index) {
                  var item = cryptoList.keys.elementAt(index);
                  //print('cryptoList.keys.elementAt($index)::$item');
                  return CryptoCard(
                    cryptoName: item,
                    cryptoPath: cryptoList.values.elementAt(index),
                    currency: currency,
                    price: cryptoItemPrice[item]!,
                  );
                },
                childCount: cryptoList.length,
              ),
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 2,
                childAspectRatio: 1.0,
              ),
            ),            
          ],
        ),
      ),
    );
  }
}

I have tried wrapping getCryptoPrices() in a setState but this still didnt resolve it. I want to have the user select the currency and then based on the selection (after the scroll has settled, using the endOfScroll boolean flag), use the new currency value to initiate http requests, populate the cryptoList Map<cryptoStr, priceStr> and then build the SliverGrid with the new Map so that things are in sync. What am I doing wrong? Thanks for your help!

I have a UI that uses http to get data from CoinApi.io. This async call populates a map that is in turn used to update the SliverGrid. This is based on a currency selection from a Cupertino app. What I find is that after the currency is selected, the price listing is always a step behind instead of matching/syncing. Here is the simple code I have thus far.

/// A widget that displays a dashboard for Bitcoin ticker information.
class BitCoinTickerDashBoard extends StatefulWidget {
  const BitCoinTickerDashBoard({super.key});

  @override
  State<BitCoinTickerDashBoard> createState() => _BitCoinTickerDashBoardState();
}

class _BitCoinTickerDashBoardState extends State<BitCoinTickerDashBoard> {
  bool endOfScroll = false;

  late Map<String, String> cryptoItemPrice = {
    'BTC': '0',
    'ETH': '0',
    'LTC': '0',
    'ADA': '0',
    'XRP': '0',
    'DOGE': '0',
    'XLM': '0',
  };

  String currency = 'USD';

  @override
  void initState() {
    currency = 'USD';
    getCryptoPrices();
    setState(() {});
    super.initState();
  }

  Text getBalance() {
    return Text(
      'USD: \$7,985,000.00',
      style: TextStyle(
          color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold),
    );
  }

  void getCryptoPrices() {
    // for all the supported cryptos construct an appropriate url
    // and using NetworkHelper, fetch the current price in the
    // current selected currency

    cryptoList.forEach(
      (crypto, _) async {
        // form the full list
        String url = '$kCoinIoApiUrl$crypto/$currency?apikey=$kCoinIoApiKeyER';

        NetworkHelper networkHelper = NetworkHelper(url: url);
        // issue the request
        var price = await networkHelper.getData();

        if (price != null) {
          double rate = price['rate'];

          cryptoItemPrice[crypto] = rate.toStringAsFixed(8);
        } else {
          cryptoItemPrice[crypto] = '-1';
        }
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      body: SafeArea(
        child: CustomScrollView(
          slivers: [           
            SliverAppBar(
              backgroundColor: Colors.white,
              expandedHeight: 200,
              toolbarHeight: 200,
              pinned: true,
              flexibleSpace: FlexibleSpaceBar(
                background: Padding(
                  padding: EdgeInsets.symmetric(horizontal: 20),
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Card(
                        elevation: 15,
                        shape: RoundedRectangleBorder(
                          borderRadius: BorderRadius.circular(10),
                        ),
                        color: Colors.deepPurple, // kPickerColor,
                        child: Padding(
                          padding: const EdgeInsets.all(20.0),
                          child: Column(
                            children: [
                              ListTile(
                                title: Text(
                                  'Your Balance',
                                  style: TextStyle(
                                    color: Colors.white,
                                    fontSize: 20,
                                    //fontWeight: FontWeight.bold,
                                  ),
                                ),
                                subtitle: getBalance(),
                                trailing: SizedBox(
                                  width: 100,
                                  child: Column(
                                    crossAxisAlignment:
                                        CrossAxisAlignment.center,
                                    children: [
                                      Expanded(
                                        flex: 3,
                                        child: NotificationListener<
                                            ScrollNotification>(
                                          onNotification: (scrollNotification) {
                                            if (scrollNotification
                                                is ScrollEndNotification) {
                                              endOfScroll = true;
                                            }
                                            return true;
                                          },
                                          child: CupertinoPicker(
                                            scrollController:
                                                FixedExtentScrollController(
                                                    initialItem: 19),
                                            backgroundColor:
                                                Colors.black87, //kPickerColor,
                                            itemExtent: 20,
                                            onSelectedItemChanged:
                                                (onSelectedItemChanged) async {
                                              currency = currenciesList[
                                                  onSelectedItemChanged];
                                              if (endOfScroll) {
                                                getCryptoPrices();
                                                setState(() {});
                                              }
                                            },
                                            children: getCurrency(),
                                          ),
                                        ),
                                      ),
                                      Expanded(child: SizedBox(height: 10)),
                                      Expanded(
                                        flex: 2,
                                        child: Text(
                                          'Currency',
                                          style: TextStyle(
                                            color: Colors.white,
                                            fontSize: 15,
                                          ),
                                        ),
                                      ),
                                    ],
                                  ),
                                ),
                              ),
                            ],
                          ),
                        ),
                      ),                      
                    ],
                  ),
                ),
              ),
            ),
            SliverGrid(
              delegate: SliverChildBuilderDelegate(
                (context, index) {
                  var item = cryptoList.keys.elementAt(index);
                  //print('cryptoList.keys.elementAt($index)::$item');
                  return CryptoCard(
                    cryptoName: item,
                    cryptoPath: cryptoList.values.elementAt(index),
                    currency: currency,
                    price: cryptoItemPrice[item]!,
                  );
                },
                childCount: cryptoList.length,
              ),
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 2,
                childAspectRatio: 1.0,
              ),
            ),            
          ],
        ),
      ),
    );
  }
}

I have tried wrapping getCryptoPrices() in a setState but this still didnt resolve it. I want to have the user select the currency and then based on the selection (after the scroll has settled, using the endOfScroll boolean flag), use the new currency value to initiate http requests, populate the cryptoList Map<cryptoStr, priceStr> and then build the SliverGrid with the new Map so that things are in sync. What am I doing wrong? Thanks for your help!

Share Improve this question edited Mar 19 at 19:53 Emad Kerhily 6404 silver badges22 bronze badges asked Mar 18 at 6:06 ZelesZeles 114 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0

Alright, first of all, this code has some serious bug: the

forEach{} 

map construct in

getCryptoPrices()

does not honor the async tag. The first solution is to replace it with an iterative call e.g.,

for (MapEntry<String, String> mapItem in cryptoMap.entries) {
..
}

Secondly, I used a RiverPod provider to watch the cryptoRepository and then build the crypto cards when the data was available i.e.,

final cryptoPriceRepositoryAsync =
                          ref.watch(cryptoPricesProvider(currency));

                      return cryptoPriceRepositoryAsync.when(
                        data: (cryptoList) {...

This worked really well for me!

I hope this helps the next person stumped by this.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论