问题
I have a function in firebase that is intended to get all documents in a collection called history
and add it to a list intended to be returned.
The problem is, and I think this is because .listen()
is async, the list returned is always empty. I know the data is queried correctly because I can print the doc in the forEach
function, but adding to a list outside doesn't work.
By the way, myStream
is a snapshot of the history collection in FireStore.
Does my function need to be a Future? How can I make this work?
Here is my code:
List getHistory(String id) {
List history;
Firestore.instance.collection(id).snapshots().listen((data) {
data.documents.forEach(
(doc) => history.add(doc['day']),
);
});
return history;
}
回答1:
Yes, your function will have to return a Future.
As you correctly pointed out, the way Firestore data is retrieved is asynchronous. This means that whereever you want to use this data, you will have to await
a Future
.
If you want to do this in your UI, you can just make use of Flutter's FutureBuilder.
However, firstly you need to refactor your getHistory
function.
You will not be able to use the snapshots function if you just want to get the history once. If you do want to use real-time updates, then you can just translate everything I say to a StreamBuilder, but then you cannot return your List
, you could only yield
it (I will just include an example implementation at the end).
Having said that, I am going to use the getDocuments method for the FutureBuilder
example.
Get data once
Future<List> getHistory(String id) async {
List history;
final List<DocumentSnapshot> documents =
(await Firestore.instance.collection(id).getDocuments()).documents;
history = documents.map((documentSnapshot) => documentSnapshot['day']).toList();
return history;
}
Notice that I added the async
keyword to be able to await
the getDocuments
call and wrapped the return type in a Future
. I used the List.map method to map the DocumentSnapshot
's to your day
value (I also changed your myStream
to a Firestore call because I cannot know the objects you actually use in your class, thus you will have to exchange that with how you access your Firestore collection).
In the next step, I will include an example implementation of how you would use this function in your UI using FutureBuilder
:
FutureBuilder(
future: getHistory(id),
builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
if (snapshot.hasError) return Text('${snapshot.error}');
if (!snapshot.hasData) return CircularProgressIndicator();
return ListView(
children: snapshot.data
.map((day) => ListTile(
title: Text('$day'),
))
.toList(),
);
},
);
I simply used a ListView
to demonstrate how you could use your list of data in your UI.
Real-time updates
Because I mentioned it earlier, here would be an example implementation for having real-time updates using Stream
(you would have to exchange your FutureBuilder
with a StreamBuilder
if you wanted to use it in your UI):
Stream<List> getHistory(String id) {
final Stream<QuerySnapshot> documents = Firestore.instance.collection(id).snapshots();
return documents.map((querySnapshot) {
List history;
final documents = querySnapshot.documents;
history = documents.map((documentSnapshot) => documentSnapshot['day']);
return history;
});
}
In this case, I am just mapping the Stream
, which will apply the mapping operation for every data event. I mentioned yield
above, but it is not necessary to use that here. You could use yield
in the following manner, which would be closer to your initial implementation:
Stream<List> getHistory(String id) async* {
await for (QuerySnapshot querySnapshot in Firestore.instance.collection(id).snapshots()) {
List history;
final documents = querySnapshot.documents;
history = documents.map((documentSnapshot) => documentSnapshot['day']);
yield history;
}
}
This is probably just confusing and it is completely unnecessary here, so just ignore it :)
来源:https://stackoverflow.com/questions/57263974/returning-list-from-firestore-listen-callback