问题
I'm trying to find out how to sum a value stored on every document inside a Firestore colletion, using the "cloud_firestore" package from Flutter.
I tried this code:
double queryValues() {
total = 0.0;
Firestore.instance
.collection('myCollection')
.snapshots()
.listen((snapshot) {
snapshot.documents.forEach((doc) => this.total +=
doc.data['amount']);
});
debugPrint(this.total.toString());
return total;
}
But I end up printing 0.0 as result. If I remove the first line (total = 0.0.) I get the sum to work, but if I reload the values are doubling (adding up to the old total).
I known they say the best solution is to store this aggregated value using a Cloud Function on every document write, but that would be a pain as I want to make totals by some range periods available for the user to query (month, year, week). Those queries result on a small number of documents, so for me it would be ideal just to iterate over the resulting collection).
This is my first question posted and I really appreciate any thoughts on this.
回答1:
If you are using an async function either you do all your work inside the listen() method or wait outside it til you are sure everything has finished inside.
The "Flutter" way of handling the second case is using setState(). You build() the widget tree with the initial outside value and when the async function finishes you call setState() so the widget tree is rebuilt with the new value.
(As a side note, inside the async function you could use the fold() list method that accumulates on the previous operation).
Here is a complete simple app that would use the external total value:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
double total = 0.0;
@override
initState() {
super.initState();
queryValues();
}
void queryValues() {
Firestore.instance
.collection('myCollection')
.snapshots()
.listen((snapshot) {
double tempTotal = snapshot.documents.fold(0, (tot, doc) => tot + doc.data['amount']);
setState(() {total = tempTotal;});
debugPrint(total.toString());
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: Text("Total: $total")),
);
}
}
回答2:
CollectionReference _documentRef=Firestore.instance.collection("User");
_documentRef.getDocuments().then((ds){
if(ds!=null){
ds.documents.forEach((value){
print(value.data);
});
}
});
回答3:
As Doug commented, data is loaded from Firestore asynchronously. By the time you print the sum, data hasn't loaded yet, the forEach hasn't run yet, and the total will still be 0.0.
It's easiest to see what's happening by placing a few print statements:
print("Before calling listen");
Firestore.instance
.collection('myCollection')
.snapshots()
.documents((snapshot) {
print("Got data");
}
print("After calling listen");
When you run this code, it prints:
Before calling listen
After calling listen
Got data
This is probably not the order you expected. But it explains why you get no result and a sum of 0.0: the data hasn't loaded yet.
To solve this you need to use Dart's async/await keywords.
double queryValues() async {
total = 0.0;
docs = await Firestore.instance
.collection('myCollection')
.snapshots()
.documents((snapshot);
docs.forEach((doc) => this.total += doc.data['amount']));
debugPrint(this.total.toString());
return total;
}
You'll notice I also replaced listen() with get(), since you only want to get the documents once (a listen() will stay active, which means you can't return a result).
And then call it with:
sum = await queryValues();
print(sum)
来源:https://stackoverflow.com/questions/52729497/whats-the-best-way-to-iterate-through-documents-on-a-firestore-collection-summi