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.
Related
I am pretty new to Flutter and I have spent some time on this and cannot find a clear answer for what I am trying to do. I am querying a collection for a couple of docs and then trying to update them using the batch function. However, I am having trouble understanding the documentation and previous answers on how exactly to use the batch function in Flutter with Firestore with multiple docs. The query seems to be working and returning the docs, but the batch.update() is throwing an error that I cannot figure out how to solve. Here is what I have
InkWell(
onTap: () async {
final batch = FirebaseFirestore.instance.batch();
var pushNotifications = await FirebaseFirestore.instance
.collection('collection name')
.where(first filter)
.where(second filter)
.get();
print(pushNotifications.docs.length);
//this is where the error is occuring when trying to update the batch
for (var postDocs in pushNotificiations.docs) {
batch.update(postDocs.reference, {
"user_refs" : usersInchallenge
}
);
await batch.commit();
)
UPDATE: I am getting closer but now it is only updating the first document of the batch and not the rest. It is giving the error below with the udpated code snippet.
[VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: Bad state: This batch has already been committed and can no longer be changed.
After banging my head against a wall for a few hours, here is what finally worked with batch() after querying multiple docs.
final batch = FirebaseFirestore.instance.batch();
var pushNotificiations = await FirebaseFirestore.instance
.collection('ff_user_push_notifications')
.where(filter 1)
.where(filter 2)
.get();
print(pushNotificiations.docs.length);
pushNotificiations.docs.forEach((doc) => {
batch.update(doc.reference, {
"user_refs" : usersInchallenge
}),
});
batch.commit();
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
I am trying to get the most recent ID of from my orders table to that I can then use this ID in a path to get the order number and increment it by one.
void _activateListeners() {
final recentOrder = _readRef.child('Orders').limitToLast(1).get();
final readOrder = _readRef
.child('Orders/{$recentOrder}/orderNum')
.onValue
.listen((event) {
final orderNum = event.snapshot.value;
setState(() {
lastOrderNum = orderNum.toString();
intOrderNum = int.parse(lastOrderNum) + 1;
newOrderNum = intOrderNum.toString();
});
});
}
I know that I need to use the "key" word somehow but I have been unable to solve this problem.
my database table looks as follows:
Orders
-N3ZdY6LOL_9Z-6KXHnK
*cartProduct
*dateTime:"2022-06-02 15:41:20.470139"
*orderNum:"6"
*totalAmount:45
-N3ZdgEQIzsjLA5NCu3U
*cartProduct
*dateTime:"2022-06-02 15:41:20.470139"
*orderNum:"7"
*totalAmount:45
Edit: can someone tell me if it is even possible to fetch the unique push ID after it has been made (not directly after the order is added)? I have looked at all the documentation and I have failed to implement anything. I have also found numerous similar questions that are either just different or unanswered.
If I know SQL is SQLite a better alternative?
You can get the key from the snapshot by using the snapshot's key property, like in this line below:
String? eventKey = event.snapshot.key;
Check out the documentation for Flutter's Realtime Database's DataSnapshot class for more information.
I am trying to get documents with their own subcollections, from Stream, but I am stuck.
This is where I set up my StreamSubscription:
Future<void> _toggleOrdersHistorySubscription({FirebaseUser user}) async {
_ordersHistorySubscription?.cancel();
if (user != null) {
_ordersHistorySubscription = ordersRepository
.ordersHistoryStream(userId: user.uid)
.listen((ordersSnapshot) {
final List<OrderModel> tmpList = ordersSnapshot.documents.map((e) {
// e.reference.collection("cart").getDocuments().;
return OrderModel.orderFromSnapshot(e);
}).toList();
add(OrdersHistoryUpdated(ordersHistory: tmpList));
});
}
}
The issue is that I can't see a way to get subcollection along with the parent document because getDocuments returns a Future.
Anyone can clear this issue for me?
So, I update the code method a separate method for retrieving data when listener is triggered but it doesn't work fully and I do not understand what's happening and why part of the code is working and part is not.
List<OrderModel> _getOrdersHistory({
#required QuerySnapshot snapshot,
}) {
return snapshot.documents.map((document) {
List<OrderedProductModel> cart = [];
List<AddressModel> addresses = [];
document.reference.collection("cart").getDocuments().then((snapshot) {
snapshot?.documents?.forEach((doc) {
cart.add(OrderedProductModel.fromSnapshot(doc));
});
});
document.reference
.collection("addresses")
.getDocuments()
.then((snapshot) {
snapshot?.documents?.forEach((doc) {
addresses.add(AddressModel.addressFromJson(doc.data));
});
});
final order = OrderModel.orderFromSnapshot(
document,
restaurantCart: cart,
);
return order.copyWith(
orderAddress:
(addresses?.isNotEmpty ?? false) ? addresses.first : null,
sentFromAddress:
(addresses?.isNotEmpty ?? false) ? addresses.last : null,
);
})
.toList() ??
[];
}
As an alternate solution to my original issue is that I made a map entry in Firestore instead of a collection for 2 address documents (which are set above as orderAddress and sentFromAddress) and for the cart I decided to get the data when needed for every cart item.
So the method which I put in the update is not the final one, but I want to understand what is happening up there as I do not understand why:
Why the cart is shown as empty if I do a print(order); right before the return and in the bloc it has data;
Why the orderAddress and sentFromAddress are both empty no matter what I try;
To be short: You'll never be able to get a List synchronously if you get the data async from firebase.
Both questions have the same answer:
Your timeline:
For each document
Create an empty list
Initiate the firebase query - getDocuments()
Subscribe to the returned future with - .then((snapshot){cart.add(...)}).
This lambda will be invoked when the documents arrived.
Another subscribe
Save your empty cart and first/last of empty addresses to an OrderModel
Your List contains the references to your empty lists indirectly
Use your bloc, some time elapsed
Firebase done, your callbacks starts to fill up your lists.
Regarding your comment like stream.listen doesn't like async callbacks:
That's not true, you just have to know how async functions work. They're run synchronously until the first await, then return with an incomplete future. If you do real async things you have to deal with the consequences of the time delay like changed environment or parallel running listeners.
You can deal with parallel running with await for (T v in stream) or you can use subscription.pause() and resume.
If anything returns a future, just do this:
...getDocuments().then((value) => {
value is the item return here. Do something with it....
})
Also, you might want to split your method up a bit to share the responsibility.
If getDocuments is a Future function and you need to wait for it, I think you should add await before it. I also don't see the snapshot status checking in your code pasted here. Maybe you have already checked the snapshot status in other function? Make sure the snapshot is ready when using it.
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()));
}
}