This question already has an answer here:
How to wait for forEach to complete with asynchronous callbacks?
(1 answer)
Closed 6 months ago.
I need to upload a list of files to Firebase storage, so ive tried the following:
Future uploadImage(List<File>? fileList) async {
List<String> urlList = [];
compressedImageFromDevice?.forEach((file) async {
final ref = await FirebaseStorage.instance
.ref()
.child("img")
.child(id)
.child(time);
final result = await ref.putFile(file);
final String mediaUrl =
await (await result.ref.getDownloadURL()).toString();
urlList.add(mediaUrl);
print(urlList.length()); //List contains an url
});
print(urlList.length()); //Outside of the loop the list keeps empty}
}
Why is the list outside of the loop empty?
Sorry but inserting code didn`t worked correctly.
use for loop like below
for(var file in compressedImageFromDevice){
}
avoid forEach
Related
I have a Riverpod Streamprovider that manages how a number of different Firebase documents are presented to the user. The user can then access each document, make some changes and return to the list of their documents. Once they have made, changes the row for that document should have a tick showing. The only wrinkle is that these documents in a different collection, each with their own identifier. So its not as easy as just streaming a whole collection, my function needs to get the identifier for each item and then get a list of documents to send to the user.
I have the code so it 'just works' but what I can't work out is why updating the record works when all the code is inside the provider vs when the provider calls it a external code. For example this StreamProvider works as I want and updated documents are recognised
final outputStreamProvider = StreamProvider.autoDispose((ref) async* {
final List<itemModelTest> itemList = [];
final user = ref.watch(loggedInUserProvider);
final uid = ref.watch(authStateProvider).value!.uid;
for (String ident in user.value!.idents) {
# get each item by its own identifier
final item = FirebaseFirestore.instance
.collection('items')
.where("ident", isEqualTo: ident)
.limit(1)
.snapshots();
final result = await item.first;
final test = result.docs[0];
final itemItem = itemModelTest.fromFirebaseQuery(test, uid);
itemList.add(itemItem);
# Listen for changes in the items
item.listen((event) async {
dev.log('event changed');
for (var change in event.docChanges) {
if (change.type == DocumentChangeType.modified) {
itemModelTest updatedModel =
itemModelTest.fromFirebaseQuery(test, uid);
itemList
.removeWhere((element) => element.title == updatedModel.title);
itemList.add(updatedModel);
}
}
});
}
yield itemList;
});
But as you can see it contains a lot of logic that doesn't belong there and should be with my firebase database class. So I tried to split it so now in my firebase crud class I have almost identical code:
Stream<List<itemModelTest>> itemsToReviewStream(LoggedInUser user, String uid) async*{
final List<itemModelTest> itemList = [];
for (String ident in user.idents) {
final item = FirebaseFirestore.instance
.collection('items')
.where("ident", isEqualTo: ident)
.limit(1)
.snapshots();
final result = await item.first;
final test = result.docs[0];
final itemItem = itemModelTest.fromFirebaseQuery(test, uid);
itemList.add(itemItem);
item.listen((event) async {
dev.log('event changed ${event.docChanges.first.doc}');
for(var change in event.docChanges){
if(change.type == DocumentChangeType.modified){
itemModelTest updatedModel = itemModelTest.fromFirebaseQuery(test, uid);
itemList.removeWhere((element) => element.title == updatedModel.title);
itemList.add(updatedModel);
}
}
});
}yield itemList;
}
and my StreamProvider now looks like this
// Get a list of the currently logged in users papers to review
final testitemStreamProvider = StreamProvider.autoDispose((ref) {
final user = ref.watch(loggedInUserProvider).value;
final uid = ref.watch(authStateProvider).value!.uid;
return DataBase().itemsToReviewStream(user!, uid);
});
The only problem is using this second approach the updates to firebase don't trigger any updates to the ui so when the user returns to their list of documents they cant see which have been processed already. I have been round the houses trying to work out what I am doing wrong but cant see it.
Edit: just a quick edit in case it matters but this is for FlutterWeb not iOS or Android
I am leaving this in case anyone else has the same problem. The real problem with this project was that the database structure was not fit for purpose and a further restriction was to not duplicate data on the database.
The simplest solution (and if you happen to be reading this because you fixing a similar problem) is to make a copy of the documents the user is supposed to have access to in their own collection, this can then be streamed as an entire collection. Checking which documents have and have not been looked at by users was always going to have to be done via an admin account anyway, so it's not as though this would have incurred a penalty.
All the same to manage my particular data repo i ended up
1 make a simple provider to stream a single document
final getSinglePageProvider = StreamProvider.family((ref, String pageId){
return DataBase().getSinglePage(pageId);});
Then once you have a list of all the documents the user has access to make a provider that provides a list of providers above
final simpleOutputsStreamsProvier = StreamProvider((ref) async* {
final user = ref.watch(loggedInUserProvider);
final items = user.value!.items;
yield items.map((e) => ref.watch(getSinglePageProvider(e))).toList();
});
You can then use this in a consumer as normal, but it has to be 'consumed' twice. In my case, I watched the ListProvider in the build method of a ConsumerWidget. That gives you a list of StreamProviders for individual pages. Finally I used ListView to get each StreamProvide (listofProviders[index]) and unwrapped that with provider.when(...
I have no idea how brittle this approach will turn out to be however!
This question already has answers here:
How to Async/await in List.forEach() in Dart
(7 answers)
Closed 1 year ago.
I wrote this piece of code and it is causing some problems:
static getFriendsRequests({required id}) async {
QuerySnapshot snapshot = await friendsRef
.doc(id)
.collection('received')
.get();
List<friendRequestItem> feedItems = [];
snapshot.docs.forEach((doc) async{
DocumentSnapshot extracted = await usersRef.doc(doc.reference.id).get();
MyUser tempUser = MyUser.fromJsonDocument(extracted);
String url = '';
tempUser.profilePictureURL=='' ? null : url=tempUser.profilePictureURL;
FriendRequest fr = FriendRequest(
userID: tempUser.userID, uniqueName: tempUser.uniqueName,
name: tempUser.name, mediaURL: url);
print(fr.uniqueName+fr.name+fr.userID+fr.mediaURL);
feedItems.add(friendRequestItem(friendReq: fr));
});
return feedItems;}
To help reading the code, the first query is to get a list of documents, each referring to a unique id. In the second, for each document I search again in the database for some data of the given id. Now I noticed that the problem should be related to the async methods because all the data I get are correct but probably the return happens before the end of the second method. How can I solve? Tell me if you need more infos about other parts of the code. Thank you
I had the same problem (How to await a Map.forEach() in dart).
To solve this issue, you need to use a for (var mapEntry in map) {}. The map would be the map would be the map you are looping through. This also works for Lists or any Iterable<T>. See the question I linked for more detail.
Please let me know if you need anymore help or anything with my answer is not working.
[This image contains the part of code wherEin I am getting the data from the website https://arprogramming.blogspot.com/ and storing the data in 3 separate lists. The link list is used to store the link of the blog so that I can use it as a link afterwards to redirect to the site from the app]2
These are all my imports
Thia is my pubspec.yaml file
This is the part of code where I am using the scraped data
THIS IS MY ERROR
Below is my main code
Future<void> _getDataFromWeb() async{
var uri =Uri.parse('https://arprogramming.blogspot.com/');
final response = await http.get(uri);
dom.Document document = parser.parse(response.body);
final elements = document.getElementsByClassName('entry-title');
final content = document.getElementsByClassName('entry-content');
final link1 = document.getElementsByClassName('entry-header blog-entry-header');
setState(() {
title = elements.map((elements)=> elements.getElementsByTagName("a")[0].innerHtml.toString()).toList();
post = content.map((content)=> content.getElementsByTagName("p")[0].innerHtml.toString()).toList();
link = link1.map((link1) async => link1.getElementsByTagName("a")[0].attributes['href']).cast<String>().toList();
});
}
You can not use the Future as string. Because these may not be available when you want to use. Use "then", it allows us to know when the async function ends and we have variable to use.
NOTE: Please be more careful further repositories. Community should upload their code as code snippet and error messages clearly. Welcome
http.get(uri).then((String response){
dom.Document document = parser.parse(response.body);
final elements = document.getElementsByClassName('entry-title');
final content = document.getElementsByClassName('entry-content');
final link1 = document.getElementsByClassName('entry-header blog-entry-header');
setState(() {
title = elements.map((elements)=> elements.getElementsByTagName("a")[0].innerHtml.toString()).toList();
post = content.map((content)=> content.getElementsByTagName("p")[0].innerHtml.toString()).toList();
link = link1.map((link1) async => link1.getElementsByTagName("a")[0].attributes['href']).cast<String>().toList();
});
});
I'm having some problems with a line in the function below. The function is handling async gets from Firebase Storage. I'm using it to get the names and urls of files I have stored there.
The issues is with getting the Urls. Specifically on the line:
String url = element.getDownloadURL().toString();
getDownloadedURL() is a Firebase future. I tried to await it, but it won't recognise the await, I guess due to "element".
The over all effect is that when I'm using this in my UI via a Future builder, the name comes out fine but the Url doesn't. It is being retrieved as the print statement shows it. But it's not being waited for, so the UI is already updated.
Been trying lots of things, but haven't found a solution, so any help would be greatly appreciated.
Future<void> getImageData() async {
final imagesFromStorage = await fb
.storage()
.refFromURL('gs://little-big-deals.appspot.com')
.child('images')
.listAll();
imagesFromStorage.items.forEach((element) {
print(element.name);
String url = element.getDownloadURL().toString();
print(url.toString());
imageData.add(ImageData(element.name, url.toString()));
});
}
Many thanks
You can't use async in forEach.
Just use a for loop:
Future<void> getImageData() async {
final imagesFromStorage = await fb
.storage()
.refFromURL('gs://little-big-deals.appspot.com')
.child('images')
.listAll();
for (var element in imagesFromStorage.items) {
print(element.name);
String url = (await element.getDownloadURL()).toString();
print(url.toString());
imageData.add(ImageData(element.name, url.toString()));
}
}
I have a simple table from which I'm fetching a list of records. Once I get the records, then I have to get information online for each of the records. The code to do this is as follows:
class UserStationList {
List<UserStationListItem> _userStations = [];
final StreamController<HomeViewState> stateController;
UserStationList({#required this.stateController});
Future fetchUserStations() async {
stateController.add(HomeViewState.Busy);
//Fetch stations from table.
List<Map<String, dynamic>> stations =
await UserStationDatabase.instance.queryAllRows();
//If there are no stations, return and tell the screen to display the no data message.
if (stations.length == 0) {
stateController.add(HomeViewState.NoData);
return;
}
//Loop through each of the stations in the list and build the collection.
stations.forEach((station) async {
UserStationListItem newItem =
await _getPurpleAirSiteData(station['_id'], station['stationid']);
_userStations.add(newItem);
});
//When done, let the screen know.
stateController.add(HomeViewState.DataRetrieved);
}
Future<UserStationListItem> _getPurpleAirSiteData(
int id, int stationId) async {
var response = await http.get('$kURL$stationId');
var data = json.decode(response.body);
return UserStationListItem(
id: id, stationId: stationId, stationName: data['results'][0]['Label']);
}
}
The problem that I am running into involves the futures. I am processing the loop in a forEach and calling into the _getPurpleAirSiteData function for each. Within that function I have to await on the http.get to bring in the data. The stateController.add(HomeViewState.DataRetrieved) function is being called and the function exits long before the loop is completed. This is resulting in the data not being available when the StreamBuilder that I have receiving the data is run.
How can I set this up so that the loop runs completely before calling stateController.add?
I would change this part of code to a list of Futures and await-ing on it.
//Loop through each of the stations in the list and build the collection.
stations.forEach((station) async {
UserStationListItem newItem =
await _getPurpleAirSiteData(station['_id'], station['stationid']);
_userStations.add(newItem);
});
To:
List<Future<UserStationListItem>> listOfFutures = [];
stations.forEach((station) {
listOfFutures.add(_getPurpleAirSiteData(station['_id'], station['stationid']));
});
var stationItems = await Future.wait(listOfFutures);
stationItems.forEach((userStationListItem) {
_userStations.add(userStationListItem);
});
What I am essentially doing creating a list of Futures with your server request. Then await on it which returns a list of item result Maintaining index, which in turn ensures that requests are completed before you hit statecontroller.add. You also gain a performance gain since all request are not going one by one and instead asynchronously. Then you just iterate through the future result and add it to your item list.