I'm new to programming, and this is my first post on stack overflow! In building my first Flutter app I'm trying to understand the code, and I'm not sure why two pieces of code behave differently. Feel free to just look at the code and answer why one doesn't work... or if you'd like, here's the background.
Data structure:
Collection: chatters Document: chatter doc SubCollection: members-chatter, another SubCollection: approved-chatter Documents: member doc, and approved doc
I'm listing all the chatter docs that a user is a member of, so from a CollectionGroup query with uid in the member doc, I then lookup the parent doc id. Next I want to have chatter docs be marked bool public, and for !public chatters, I only want them listed if the user's uid is also on an approved doc in SubCol approved-chatter.
So my main question is why the await doesn't hold through the entirety of the nested .then
's in my first attempt.
But while I'm here, I'm also open to any insight on my approach to handling membership to groups of both public and approval-required types. It seems trickier than I first thought, once considering read/write permissions and appropriate security and privacy.
I tried this first.
// get my chatter IDs
Future<List<String>> oldgetMyChatterIDs() async {
List<String> myChatterIDs = [];
await FirebaseFirestore.instance
.collectionGroup('members-chatter')
.where('uid', isEqualTo: FirebaseAuth.instance.currentUser?.uid)
.where('status', isEqualTo: 'joined')
.orderBy('timeLastActive', descending: true)
.get()
.then(
(snapshot) => snapshot.docs.forEach((document) {
document.reference.parent.parent!.get().then((value) {
// the above 'then' isn't awaited.
if (value.data()?['public'] ?? true) {
myChatterIDs.add(document.reference.parent.parent!.id);
// 'myChatterIDs' is returned empty before the above line fills the list.
} else {
// check if user is approved.
}
});
}),
);
// // adding the below forced delay makes the code work... but why aren't the 'thens' above working to holdup the return?
// await Future.delayed(const Duration(seconds: 1));
return myChatterIDs;
}
but return myChatterIDs;
completes before:
document.reference.parent.parent!.get().then((value) {
if (value.data()?['public'] ?? true) {
myChatterIDs.add(document.reference.parent.parent!.id);
}
Why doesn't the return await the await?
I rewrote the code, and this way works, but I'm not sure why it's different. It does appear a bit easier to follow, so I perhaps it's better this way anyway.
// get my chatter IDs
Future<List<String>> getMyChatterIDs() async {
List<String> myChatterIDs = [];
QuerySnapshot<Map<String, dynamic>> joinedChattersSnapshot = await FirebaseFirestore
.instance
.collectionGroup('members-chatter')
.where('uid', isEqualTo: FirebaseAuth.instance.currentUser?.uid)
.where('status', isEqualTo: 'joined')
.orderBy('timeLastActive', descending: true)
.get();
for (var i = 0; i < joinedChattersSnapshot.docs.length; i ) {
DocumentSnapshot<Map<String, dynamic>> aChatDoc =
await joinedChattersSnapshot.docs[i].reference.parent.parent!.get();
bool isPublic = aChatDoc.data()?['public'] ?? true;
if (isPublic) {
myChatterIDs.add(aChatDoc.id);
} else {
try {
DocumentSnapshot<Map<String, dynamic>> anApprovalDoc =
await FirebaseFirestore.instance
.collection('chatters')
.doc(aChatDoc.id)
.collection('approved-chatter')
.doc(FirebaseAuth.instance.currentUser!.uid)
.get();
bool isApproved = anApprovalDoc.data()!['approved'];
if (isApproved) {
myChatterIDs.add(aChatDoc.id);
}
} catch (e) {
// // Could add pending to another list such as
// myPendingChatterIDs.add(aChatDoc.id);
}
}
}
return myChatterIDs;
}
CodePudding user response:
Take a look at this piece of code:
print("1");
print("2");
result:
1
2
The print on both lines is synchronous, they don't wait for anything, so they will execute immediately one after the other, right ?
Take a look at this now:
print("1");
await Future.delayed(Duration(seconds: 2));
print("2");
result:
1
2
This now will print 1, then wait 2 seconds, then print 2 on the debug console, using the await
in this case will stop the code on that line until it finishes ( until the 2 seconds pass).
Now take a look at this piece of code:
print("1");
Future.delayed(Duration(seconds: 2)).then((_) {print("2");});
print("3");
result:
1
3
2
this seems to be using the Future
, but it will not wait, because we set the code synchronously, so 1 will be printed, it will go to the next line, it will run the Future.delayed
but it will not wait for it across the global code, so it will go to the next line and print 3 immediately, when the previous Future.delayed
finishes, then it will run the code inside the then
block, so it prints the 2 at the end.
in your piece of code
await FirebaseFirestore.instance
.collectionGroup('members-chatter')
.where('uid', isEqualTo: FirebaseAuth.instance.currentUser?.uid)
.where('status', isEqualTo: 'joined')
.orderBy('timeLastActive', descending: true)
.get()
.then(
(snapshot) => snapshot.docs.forEach((document) {
document.reference.parent.parent!.get().then((value) {
// the above 'then' isn't awaited.
if (value.data()?['public'] ?? true) {
myChatterIDs.add(document.reference.parent.parent!.id);
// 'myChatterIDs' is returned empty before the above line fills the list.
} else {
// check if user is approved.
}
});
}),
);
// // adding the below forced delay makes the code work... but why aren't the 'thens' above working to holdup the return?
// await Future.delayed(const Duration(seconds: 1));
return myChatterIDs;
using then
, the return myChatterIDs
will execute immediately after the Future
before it, which will cause an immediate end of the function.
To fix this with the then
, you need simply to move that return myChatterIDs
inside the then
code block like this:
/*...*/.then(() {
// ...
return myChatterIDs
});
using the await
keyword in your second example will pause the code on that line until the Future
is done, then it will continue for the others.
you can visualize what's happening live on your code, by setting breakpoints on your method lines from your IDE, then see what's running first, and what's running late.
CodePudding user response:
It´s because document.reference.parent.parent!.get()
is a Future too but you aren't telling your function to wait for it. The first await only applies to the first Future.
When you use then
you are basically saying, execute this Future and when it's finished, do what is inside this function but everything outside that function doesn't wait for the then
to finish unless you told it to with an await
.
For example this code:
String example = 'Number';
await Future.delayed(const Duration(seconds: 1)).then(
(value) {
//Here it doesn't have an await so it will execute
//asynchronously and just continues to next line
//while the Future executes in the background.
Future.delayed(const Duration(seconds: 1)).then(
(value) {
example = 'Letter';
},
);
},
);
print(example);
Will result in Number
being printed after 1 second. But this code:
String example = 'Number';
await Future.delayed(const Duration(seconds: 1)).then(
(value) async {
//Here it does have an await so it will wait for this
//future to complete too.
await Future.delayed(const Duration(seconds: 1)).then(
(value) {
example = 'Letter';
},
);
},
);
print(example);
Will result in Letter
being printed after 2 seconds.
Additional to this, forEach cannot contain awaits as mentioned here in the Dart documentation and even if you use it will still be asynchronous.
Try using
for(var document in snapshot.docs){
//Your code here
}
Instead