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 badges1 Answer
Reset to default 0Alright, 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.