saving a value from a document snapshot from firestore - flutter

in the following code i want to save the String data that i get from firestore inside the .then() method into another variable. but once its outside the method i lose the value.
String memberName = '';
member.user.get().then(
(DocumentSnapshot documentSnapshot) {
if (documentSnapshot.exists) {
memberName = documentSnapshot.get('Full_Name');
print(memberName) //'John Smith'
}
},
);
print(memberName) //''
posting all the code for reference.
Widget buildGroupMemberTile(GroupMember member, bool loggedUserIsAdmin) {
final memberDoc = FirebaseFirestore.instance
.collection('groups')
.doc(widget.group.id)
.collection("members")
.doc(member.email);
print(getMemberName(member));
String memberName = '';
member.user.get().then(
(DocumentSnapshot documentSnapshot) {
if (documentSnapshot.exists) {
memberName = documentSnapshot.get('Full_Name');
print(memberName) //'John Smith'
}
},
);
print(memberName) //''
return ListTile(
onTap: (() {
if (loggedUserIsAdmin) {
if (member.role == 'admin') {
memberDoc.update({'role': 'member'});
} else if (member.role == 'member') {
memberDoc.update({'role': 'admin'});
}
}
}),
title: Center(
child: Padding(
padding: EdgeInsets.fromLTRB(0, 5, 0, 5),
child: Container(
width: 450,
decoration: BoxDecoration(
color: Color.fromARGB(255, 65, 61, 82),
borderRadius: BorderRadius.all(Radius.circular(12))),
child: Padding(
padding: const EdgeInsets.fromLTRB(40, 20, 40, 20),
child: Column(
children: [
Text(
memberName,
style: GoogleFonts.poppins(
color: ThemeColors.whiteTextColor,
fontSize: FontSize.large,
fontWeight: FontWeight.w400,
),
),
],
),
),
),
),
));
}
Im guessing that I'm referencing documentSnapshot.get('Full_Name') and not copying its the value to memberName. How can i keep the value?

try to get it like this without the then, and call the data() first to get the Map<String, dynamic> of the docuemnt:
DocumentSnapshot documentSnapshot = await member.user.get();
if (documentSnapshot.exists) {
memberName = (documentSnapshot.data() as Map<String, dynamic>)["Full_Name"];
print(memberName);
}
Note: you need to mark the method where this is implemented to Future with async keyword

Looks like you need to put the entire member.user.get() call into a async future method & place an await keyword just before the call.
Then you need to use a FutureBuilder around the widget that will be displaying the name to ensure the name has a value before it is rendered.

Related

Future not returning String

Why do I keep getting 'Instance of...' when I'm trying to get a String. What's wrong with the function?
Future<string?> counter() async {
Future.delayed(const Duration(seconds: 5), () {
context.watch<FoodCount>().display(widget.food).toString();
return widget.food.quantity.toString();
});
int count = widget.food.quantity;
// print(count);
return count;
}
This is what I'm trying to do:
class FoodQuantity extends StatefulWidget {
final Food food;
FoodQuantity(this.food);
#override
State<FoodQuantity> createState() => _FoodQuantityState();
}
class _FoodQuantityState extends State<FoodQuantity> {
final int amount = 0;
String getCurrency() {
var format = NumberFormat.simpleCurrency(name: 'NGN');
return format.currencySymbol;
}
Future<int> counter() async {
final int result = await Future.delayed(const Duration(seconds: 5), () {
int result = context.read<FoodCount>().display(widget.food);
return result;
});
return result;
}
#override
Widget build(BuildContext context) {
return Container(
width: double.maxFinite,
height: 40,
child: Stack(
children: [
Align(
alignment: const Alignment(-1, 0), //0.3
child: Container(
width: 120,
height: double.maxFinite,
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.1),
borderRadius: BorderRadius.circular(30),
),
child: Row(
children: [
const SizedBox(width: 15), //Spacing
Text(
getCurrency(),
style: const TextStyle(
fontSize: 16, fontWeight: FontWeight.bold),
),
Text(
widget.food.price.toString(),
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
)
],
),
),
),
Align(
alignment: const Alignment(1, 0), //0.3
child: Container(
height: double.maxFinite,
width: 120,
decoration: BoxDecoration(
color: Color(0xff453658),
borderRadius: BorderRadius.circular(30),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
GestureDetector(
onTap: () {
if (context.read<Counter>().count != 0) {
context.read<Counter>().decrement();
// widget.food.quantity--;
userOrders.remove(widget.food);
context.read<FoodCount>().decrement(widget.food);
setState(() {});
} else {
context.read()<Counter>();
}
},
child: const Text(
'-',
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
),
),
),
Container(
padding: const EdgeInsets.all(12),
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
child: Text(
counter().toString(),
// context
// .watch<FoodCount>()
// .display(widget.food)
// .toString(),
// widget.food.quantity.toString(),
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
),
GestureDetector(
onTap: () {
context.read<Counter>().increment();
context.read<FoodCount>().increment(widget.food);
// widget.food.quantity++;
userOrders.add(widget.food);
setState(() {});
},
child: const Text(
'+',
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
),
],
),
);
}
}
I made a provider class FoodCount that monitors the value quantity of object type Food. The async function is supposed to simply return the quantity of the Food provided to it
Provider:
class FoodCount with ChangeNotifier {
int increment(Food food) {
food.quantity++;
int foodCount = food.quantity;
notifyListeners();
return foodCount;
}
int decrement(Food food) {
food.quantity--;
int foodCount = food.quantity;
notifyListeners();
return foodCount;
}
int display(Food food) {
int count = food.quantity;
notifyListeners();
return count;
}
void update() {
notifyListeners();
}
}
Food:
class Food {
String imgUrl;
String desc;
String name;
String waitTime;
num score;
int price;
int quantity;
bool favourited;
List<Map<String, String>> ingredients;
String about;
bool highlight;
Food(this.imgUrl, this.desc, this.name, this.waitTime, this.score, this.price,
this.quantity, this.ingredients, this.about, this.favourited,
{this.highlight = false});
}
Future.delayed is by itself a Future, so you cannot track it without an await to keep the result.
Take a look here, how you can make it, then take care of the difference about a sequential method and a Future method;
Future<String?> counter() async {
// Future.delayed is by itself a future, so you connot track it without an await to get the result
final String result = await Future.delayed(const Duration(seconds: 5), () {
var a = "I'm a Future after 5 seconds" ;
return a;
});
return result;
// Here is not the result you want because this method might be not a Future I think
// int count = widget.food.quantity;
// print(count);
// return count;
}
Or
Future<String?> counter2() async {
return await Future.delayed(const Duration(seconds: 5), () {
var a = "I'm a Future after 5 seconds" ;
return a;
});
// Here is not the result you want because this method might be not a Future I think
// int count = widget.food.quantity;
// print(count);
// return count;
}
When you work with Future and you want to get value from it, you should use await or then()
try to use this code:
await Future.delayed(const Duration(seconds: 5), () {
context.watch<FoodCount>().display(widget.food).toString();
return widget.food.quantity.toString();
});
First off, here's a tip: you're using Future.delayed as a way to get a value after a delay. Try splitting that up into two parts. Instead of
Future.delayed(const Duration(seconds: 5), () {
context.watch<FoodCount>().display(widget.food).toString();
return widget.food.quantity.toString();
});
int count = widget.food.quantity;
Try
await Future.delayed(const Duration(seconds: 5));
context.watch<FoodCount>().display(widget.food.toString());
return widget.food.quantity.toString();
Secondly, the other users are right: when you receive a Future<String>, you can't actually get to the String without awaiting it. Problem is, you can use await in an async function, and build is not async. Conceptually, think of it as "you need to wait 5 seconds for the delay, but your user needs a UI now".
You can solve this using FutureBuilder, which allows you to return some widget until the future finishes.
// In your State class:
late final Future<int> futureCounter; // the future containing your data
#override
void initState() {
// Start your counter now, before the UI loads
futureCounter = counter();
super.initState();
}
// in your build:
Container(
padding: const EdgeInsets.all(12),
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
child: FutureBuilder(
future: futureCounter,
builder: (context, snapshot) => Text(
snapshot.hasData ? snapshot.data : "Loading...",
)
style: const TextStyle(fontWeight: FontWeight.bold),
),
);

How can change my hardcoded data of a card carousel to dynamic data from api response -Flutter

Good day. Please I am have a challenge on how to change my hardcoded data to dynamic from the api. The response to the API returns success but populating the card carousel is the challenge I am having. I keep on getting error "type 'List' is not a subtype of type 'List<Map<String, String>>'"'
This my Api request:
SharedPreferences _prefs;
void getCategories() async {
try {
_prefs = await SharedPreferences.getInstance();
var _categoryService = CategoryService();
var result =
await _categoryService.getCategories(_prefs.getString('token'));
var categories = json.decode(result.body);
print('categories');
print(categories);
List<Map<String, String>> foodCategories = categories;
} catch (e) {
throw Exception();
}
}
This is my list variable with hard coded data:
final List<Map<String, String>> foodCategories = [
{
'name': 'Rice Planting',
'image': 'images/Icon-001.png',
},
{
'name': 'Harvesting',
'image': 'images/Icon-002.png',
},
{
'name': 'Machineries',
'image': 'images/Icon-003.png',
},
{
'name': 'Rice Products',
'image': 'images/Icon-004.png',
}
];
And this is my screen:
Container(
height: 105,
margin: const EdgeInsets.only(
top: 20.0,
bottom: 25.0,
),
child: ListView.builder(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.only(
left: 20.0,
),
itemCount: this.foodOptions.length,
itemBuilder: (context, index) {
Map<String, String> option = this.foodOptions[index];
return Container(
margin: const EdgeInsets.only(right: 35.0),
child: Column(
children: <Widget>[
Container(
width: 70,
height: 70,
margin: const EdgeInsets.only(bottom: 10.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(5.0),
),
image: DecorationImage(
image: AssetImage(
option['image'],
),
),
boxShadow: [
BoxShadow(
blurRadius: 10.0,
color: Colors.grey[300],
offset: Offset(6.0, 6.0),
)
],
),
),
Text(
option['name'],
style: TextStyle(fontSize: 17.0),
),
],
),
);
}),
),
My basic challenge is how to switch this hardcoded data of **foodCategories ** to
var categories = json.decode(result.body); which is from the API. any suggestion I will highly appreciate. The Api returns a json with image and category name
this is the UI picture
Declare this List in your screen widget:
final List<Map<String, String>> foodCategories = [
{
'name': 'Rice Planting',
'image': 'images/Icon-001.png',
},
{
'name': 'Harvesting',
'image': 'images/Icon-002.png',
},
{
'name': 'Machineries',
'image': 'images/Icon-003.png',
},
{
'name': 'Rice Products',
'image': 'images/Icon-004.png',
}
];
Add this method to your screen widget:
void getCategories() async {
try {
_prefs = await SharedPreferences.getInstance();
var _categoryService = CategoryService();
var result =
await _categoryService.getCategories(_prefs.getString('token'));
var categories = json.decode(result.body);
print('categories');
print(categories);
setState(() {
foodCategories = categories;
});
} catch (e) {
throw Exception();
}
}
and call the method in initState:
#override
void initState() {
super.initState();
getCategories();
}
You can use a state management tool for this use case, like Provider, on flutter.dev there is a nice explanation on how to use it Simple app state management.
Basically you wrap the api call inside a provider class and every time data comes from the api it notify the consumers that are using the provider.
Steps:
1 - Add the provider dependency to your pubspec.yaml.
name: my_name
description: Blah blah blah.
# ...
dependencies:
flutter:
sdk: flutter
provider: ^6.0.0
dev_dependencies:
# ...
2 - Create a provider for your desire state, in this case the list of categories
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../category_service.dart';
class CategoriesProvider with ChangeNotifier {
List<Map<String, String>> foodCategories = [];
bool loading = false;
var _prefs;
void getCategories() async {
try {
loading = true;
_prefs = await SharedPreferences.getInstance();
var _categoryService = CategoryService();
var result =
await _categoryService.getCategories(_prefs.getString('token'));
var categories = json.decode(result.body);
print('categories');
print(categories);
List<Map<String, String>> foodCategories = categories;
loading = false;
notifyListeners();
} catch (e) {
throw Exception();
}
}
}
3 - Create an instance of CategoriesProvider and add it to the widget tree near to the desired carrossel widget, you can read more about where to insert the provider on this link https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CategoriesProvider (),
child: const MyApp(),
),
);
}
4 - In the stateful widget you need to create a instance of the CategoriesProvider to retrieve the data from the api.
class _MyApp extends State<MyApp> {
late CategoriesProvider categoriesProvider;
#override
void initState() {
categoriesProvider = Provider.of(context, listen: false);
categoriesProvider.getCategories();
super.initState();
}
5 - On the screen widget you can wrap your widget with a Consumer widget to retrieve the data from the provider, replacing on your example the this.foodOptions with the categories defined early on the provider class.
Consumer<CategoriesProvider>(
builder: (context, provider, _) => Container(
height: 105,
margin: const EdgeInsets.only(
top: 20.0,
bottom: 25.0,
),
child: ListView.builder(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.only(
left: 20.0,
),
itemCount: provider.foodCategories.length,
itemBuilder: (context, index) {
Map<String, String> option = provider.foodCategories[index];
return Container(
margin: const EdgeInsets.only(right: 35.0),
child: Column(
children: <Widget>[
Container(
width: 70,
height: 70,
margin: const EdgeInsets.only(bottom: 10.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(5.0),
),
image: DecorationImage(
image: AssetImage(
option['image'],
),
),
boxShadow: [
BoxShadow(
blurRadius: 10.0,
color: Colors.grey[300],
offset: Offset(6.0, 6.0),
)
],
),
),
Text(
option['name'],
style: TextStyle(fontSize: 17.0),
),
],
),
);
}),
),
),

Looping through Firestore is ignoring async await?

So I have an app where I have images stored on Firebase Storage and I want to loop through a few records in my Firestore and create Containers() for each of them to be used in a PageView. However, when looping through it seems like the AWAIT clues are ignored somehow?
This is what I got:
loadDashboard() async {
// Load data about breed in focus
Map<String, dynamic> _documentData;
_isLoadingArticles = true;
_dashboardArticles.clear();
int _articleCount = 0;
await _firestore
.collection('DashboardArticle')
.where("active", isEqualTo: true)
.get()
.then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((doc) {
_articleCount++;
downloadURL(doc["image"]);
print(doc["title"]);
_dashboardArticles.add(
Padding(
padding: EdgeInsets.fromLTRB(5, 0, 5, 0),
child: Stack(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(15),
child: Image(
image: NetworkImage(
_articleImgUrl,
),
alignment: Alignment.center,
width: double.infinity,
fit: BoxFit.cover,
),
),
Padding(
padding: EdgeInsets.fromLTRB(20, 10, 0, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 25),
Text(
doc["title"],
style: TextStyle(
shadows: [
Shadow(
blurRadius: 10.0,
color: Colors.black,
offset: Offset(1.0, 1.0),
),
],
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold),
),
SizedBox(height: 5),
Text(
doc["tagline"],
style: TextStyle(
color: Colors.white,
fontSize: 16,
shadows: [
Shadow(
blurRadius: 10.0,
color: Colors.black,
offset: Offset(1.0, 1.0),
),
],
),
)
],
),
),
],
),
),
);
setState(() {
_isLoadingArticles = false;
_totalArticleDots = _articleCount;
});
});
}).catchError((e) => print("error fetching data: $e"));
}
Future<String> downloadURL(String image) async {
await firebase_storage.FirebaseStorage.instance
.ref('dashboard_article_images/$image')
.getDownloadURL()
.then((value) => {
setState(() {
_articleImgUrl = value;
print(_articleImgUrl);
})
})
.catchError((e) => {
setState(() {
_articleImgUrl = "";
})
});
return "";
}
_articleImgUrl is a variable outside that I set as I'm not sure how to get the String value from the Future in any other way.
Each PRINT shows the data in correct order, for example:
flutter: Pooch in focus: Corgi
flutter: https://firebasestorage.googleapis.com:443/v0/b/.../o/article_images%2F4f3... (I truncated here)
When I se breakpoints it seems that _articleImgUrl has no value at the time the _dashboardArticles.add(...) is executed, so it feels like the awaits are skipped when looping somehow?
Invalid argument(s): No host specified in URI file:///
Can anyone provide insights? Thanks!
Yes your await is ignored because you are not awaiting your method downloadURL, also I would not recommend to use querySnapshot.docs.forEach as you won't be able to manage asynchronous operations correctly. You should use for in instead, also you should not use await and then at the same time.
Here is some fix you could apply to your code:
final querySnapshot = await _firestore
.collection('DashboardArticle')
.where("active", isEqualTo: true)
.get().catchError((e) => print("error fetching data: $e"));
for (final doc in querySnapshot.docs) {
_articleCount++;
await downloadURL(doc['image']);
print(doc["title"]);
// rest of your loop ...
}
By doing so you ensure all of your asynchronous operations are correctly awaited.

Flutter Tried calling: [] error on list builder

I am showing list-builder i need to show just a static data right now I am just check itemCount right now later'll show data but it's showing error .
Here is my code
class _OrderPageState extends State<OrderPage> {
bool showCards = false;
var data;
#override
void initState() {
this.getOrders();
}
getOrders() async{
final storage = new FlutterSecureStorage();
String userId = await storage.read(key: "_userID");
String url =
'http://retailapi.airtechsolutions.pk/api/orders/customer/${userId}/0';
print(url);
http.Response res = await http.get(
url,
);
var data = json.decode(res.body.toString());
print(data);
if(data['description'].toString() == "Success"){
print(data['Orders']);
print(data['Orders'].length); //its printing 6 here
setState(() {
showCards = true;
});}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Order', style: Theme.of(context).textTheme.headline4),
),
body: showCards ? Container(
child: ListView.builder(
itemCount: data['Orders'].length,
shrinkWrap: true,
padding: EdgeInsets.symmetric(horizontal: 18.0, vertical: 20.0),
itemBuilder: (context, index) {
var order = orderList[index];
return SideInAnimation(index, child:GestureDetector(
onTap: () {
// Get.to(OrderDetailPage(order: order));
},
child: Container(
width: double.infinity,
padding: EdgeInsets.all(12.0),
margin: EdgeInsets.only(bottom: 15.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15.0),
border: Border.all(color: Theme.of(context).accentColor),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(order.id,
style: Theme.of(context)
.textTheme
.headline3
.copyWith(color: Theme.of(context).primaryColor)),
SizedBox(height: 12.0),
Text(order.dateOrder, style: Theme.of(context).textTheme.subtitle2),
Divider(),
orderCardItem(context,
title: "order.orderstatus", data: order.orderStatus),
SizedBox(height: 12.0),
orderCardItem(context,
title: "order.items",
data: "${order.totalItem} " + tr("order.itemspurchased")),
SizedBox(height: 12.0),
priceItem(context,
title: "order.price", data: "\$ ${order.totalPrice}"),
],
),
),
));
},
),
) : Container(),
);
}
}
In my function its printing the length by in List builder its showing an error that Tried calling: ` But on the print where API load it's showing the length 6.
First you need to initalize a list or array then simply add your data into that variable and then call this variable to your listview builder
var ordersData = [];
then your getData() method should be like this
getOrders() async {
...
if (data['description'].toString() == "Success") {
ordersData.add(data['Orders']); // Add your data to array or list
print(ordersData.length); //its printing 6 here
}
...
}
Here your Listview like this
ListView.builder(
itemCount: ordersData.length, // Here you need to pass this lenght
...
)

Map<dynamic,dynamic> is null after loading currencies from API

Any idea why my currencies map is null in the CurrencyConverterState class? I am loading currencies from an API to my currencies map in class API (see code for class API) and also trying to set state after the loading is done. After loading the currencies from the API to my currencies map I can also access the rates from the map so that means that my map is working fine and is not null. I did that test by printing a currency rate and it was fine. But that works just in the API class. The problem is that as soon as I test my currencies map in class CurrencyConverterState it says that the map is null and I am stuck in circular progress indicator (see code for class CurrencyConverterState). I dont really know why it says that the map is null. Any help appreciated.
API class code:
class API
{
var fromTextController = new TextEditingController();
Map<dynamic, dynamic> currencies;
String fromCurrency;
String toCurrency;
String result;
API(String from, String to){
fromCurrency = from;
toCurrency = to;
}
Future<dynamic> loadCurrencies() async {
String uri = "http://api.openrates.io/latest";
var response = await http
.get(Uri.encodeFull(uri), headers: {"Accept": "application/json"});
var responseBody = json.decode(response.body);
Map curMap = responseBody['rates'];
currencies = curMap;
setState(() {});
print (currencies["SEK"]);
}
Future<dynamic> doConversion() async {
String uri =
"http://api.openrates.io/latest?base=$fromCurrency&symbols=$toCurrency";
var response = await http
.get(Uri.encodeFull(uri), headers: {"Accept": "application/json"});
var responseBody = json.decode(response.body);
setState(() {
result = (double.parse(fromTextController.text) *
(responseBody["rates"][toCurrency]))
.toString();
});
setState(() {});
return "Success";
}
void setState(Null Function() param0) {}
}
CurrencyConverterState class code:
class CurrencyConverterState extends State<CurrencyConverter> {
final API api = new API("USD", "SEK");
CurrencyConverterState();
int i = 1;
#override
void initState() {
super.initState();
api.loadCurrencies();
api.fromTextController.addListener(doConversion);
setState(() {
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomPadding: false,
backgroundColor: Theme.of(context).primaryColor,
body: api.currencies?.keys == null
? Center(child: CircularProgressIndicator())
: Stack(children: [
Positioned(
top: 30,
child: Container(
alignment: Alignment.center,
height: MediaQuery.of(context).size.height / 2,
width: MediaQuery.of(context).size.width,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
elevation: 10,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
'Converter',
style: TextStyle(
color: Colors.black, fontSize: 22.0),
),
ListTile(
title: TextField(
controller: api.fromTextController,
decoration: InputDecoration(
hintText: 'Enter a number'),
style: TextStyle(
fontSize: 20.0, color: Colors.black),
keyboardType: TextInputType.numberWithOptions(
decimal: true),
),
trailing:
buildDropDownButton(api.fromCurrency),
),
ListTile(
title: Chip(
label: api.result != null
? Container(
width: 1000,
height: 40,
child: Text(
api.result,
style: Theme.of(context)
.textTheme
.headline4,
),
)
: Container(
width: 1000,
height: 40,
child: Text(" "))),
trailing:
buildDropDownButton(api.toCurrency),
),
],
)),
),
),
),
]));
}
Widget buildDropDownButton(String currencyCategory) {
return DropdownButton(
value: currencyCategory,
dropdownColor: Colors.white,
icon: Icon(Icons.arrow_downward),
iconSize: 24,
items: api.currencies.keys.map((dynamic value) => DropdownMenuItem(
value: value,
child: Row(children: <Widget>[
Text(value),
])))
.toList(),
onChanged: (dynamic value) {
if (currencyCategory == api.fromCurrency) {
api.fromCurrency = value;
} else {
api.toCurrency = value;
}
setState(() {});
});
}
doConversion() {
api.doConversion();
setState(() {});
}
}
Actually, you are retrieving currencies before they get retrieved and you are never updating the UIs when they are retrieved.
This is a possible solution:
//In API class
Future<Map<String,dynamic>> loadCurrencies() async {
String uri = "http://api.openrates.io/latest";
var response = await http
.get(Uri.encodeFull(uri), headers: {"Accept": "application/json"});
var responseBody = json.decode(response.body);
Map<String,dynamic> curMap = responseBody['rates'];
print (currencies["SEK"]);
return curMap;
}
Then in your main class use a futureBuilder:
return Scaffold(
resizeToAvoidBottomPadding: false,
backgroundColor: Theme.of(context).primaryColor,
body: FutureBuilder(
future: api.loadCurrencies(),
builder: (context, currencies) =>{
return currencies==null?
? Center(child: CircularProgressIndicator())
: Stack(/*rest of your code*/);
})
);