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;
}
2
Answers
Take a look at this piece of code:
result:
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:
result:
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:
result:
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 theFuture.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 previousFuture.delayed
finishes, then it will run the code inside thethen
block, so it prints the 2 at the end.in your piece of code
using
then
, thereturn myChatterIDs
will execute immediately after theFuture
before it, which will cause an immediate end of the function.To fix this with the
then
, you need simply to move thatreturn myChatterIDs
inside thethen
code block like this:using the
await
keyword in your second example will pause the code on that line until theFuture
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.
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 thethen
to finish unless you told it to with anawait
.For example this code:
Will result in
Number
being printed after 1 second. But this code: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
Instead