Flutter firestore pagination doesn't start after a document, - firebase

I am trying to implement a pagination feature on my list and I've been following this official documentation https://firebase.google.com/docs/firestore/query-data/query-cursors
Here is a main code:
Future<List<Review>> fetchReviewPaginated(int limit) async {
var ref = _firestore.collection('reviews').orderBy('creationDate', descending: true);
if (_isFetchingUser) return null;
_isFetchingUser = true;
if (cachedReview == null) {
var snapshot = await ref.limit(limit).get();
lastPage = snapshot.docs.last;
cachedReview = snapshot.docs.map((e) => Review.fromMap(e.data())).toList();
} else {
var snapshot = await ref.startAfter(lastPage["creationDate"]).limit(limit).get();
lastPage = snapshot.docs.last;
cachedReview.addAll(snapshot.docs.map((e) => Review.fromMap(e.data())).toList());
}
if (cachedReview.length < limit) hasNext = false;
_isFetchingUser = false;
notifyListeners();
return cachedReview;
}
My widget will call fetchReviewPaginated(5) and display what is in cachedReviews. If the cachedReviews is null, then it will fetch 5 of reviews, then it will startAfter what is in the last of the cachedReviews and get another 5 documents after that point.
Problem is that this function always returns the same 5 items and doesn't truly start after what I specify,

I cannot test/try your code at this moment but I think this is because startAfter(lastPage["creationDate"]) is not correct.
You need to pass a list of values to startAfter() (a list with a unique item in your case). Since lastPage is a DocumentSnapshot you therefore need to use get() or data(), like startAfter([lastPage.get("creationDate")]).
Note that you could instead use the startAfterDocument() method to which you pass the DocumentSnapshot itself. It is somehow more adapted to your case, since you have only one sorting.

Related

How to return Future<List<Object>> from List<DocuementReference>>?

I am trying to create a method to convert a List to Future<List>.
This is the method I created.
static Future<List<Product?>> fromProductRefList(
List<DocumentReference> ref) async {
List<Product> shopProductList = [];
ref.forEach((productRef) async {
final productDoc = productRef.get();
final product = await Product.fromDocument(await productDoc);
shopProductList.add(product!);
});
print('shopProductList: $shopProductList');
return shopProductList;
}
and called it in cubit,
void mapProductToState() async {
emit(state.copyWith(status: MyProductStatus.loadding));
final shop = _shopBloc.state.shop;
List<Product?> productList = [];
if (shop.shopProductRef.isNotEmpty) {
final productList = Product.fromProductRefList(shop.shopProductRef);
}
emit(state.copyWith(
shop: shop,
productList: productList,
status: MyProductStatus.loaded,
));
}
VScode shows no error but when I run the code, fromProductRefList return empty list. Seems like fromProductRefList did not wait for the Document actually get() from the database and just return.
When I add a second delay in fromProductRefList before returning the shopProductList, everything works as expected.
I have read another question on stackoverflow suggest using asyncMap() but I am not sure how to apply it in my case.
Edit:
When I add a delay, the method return without any issue. If not, it will return a empty list
static Future<List<Product?>> fromProductRefList(
List<DocumentReference> ref) async {
List<Product> shopProductList = [];
ref.forEach((productRef) async {
final productDoc = productRef.get();
final product = await Product.fromDocument(await productDoc);
shopProductList.add(product!);
});
await Future.delayed(const Duration(milliseconds: 500));
print('shopProductList: $shopProductList');
return shopProductList;
}
Thank you.
You are missing the await keyword, to actually wait for the call. It only compiles, because you also declare a new variable of name productList, shadowing the already existing one.
So this line:
final productList = Product.fromProductRefList(shop.shopProductRef);
should read:
productList = await Product.fromProductRefList(shop.shopProductRef);
In addition, this does not do what you think it does:
ref.forEach((productRef) async {
It does not wait for each call. Please use a normal for flow control structure and await the async call, not the forEach method. The forEach method will no wait for the Futures returned from the methods.

A function prints the value but It's not printed on another function when returned

I'm trying to get the near by location using firebase query and it goes well. This the function that gets the nearby location.
Future<List<DocumentSnapshot>> nearbyLocations() async {
CollectionReference collectionRefer =
FirebaseFirestore.instance.collection('locations');
double radius = 10;
String field = 'position';
List<DocumentSnapshot> docList;
GeoFirePoint center = await getCurrentLocation();
// print(center);
Stream<List<DocumentSnapshot>> stream = geo
.collection(collectionRef: collectionRefer)
.within(center: center, radius: radius, field: field, strictMode: true);
stream.listen((List<DocumentSnapshot> documentList) {
if (documentList.length > 0) {
print(documentList[0].data());
docList = documentList;
} else {
return {};
}
});
}
I know that the query will return only one data. So, I printed on the first value in the above function.
The problem arises when the documentList is returned.
loadData() async {
documentList =
await GeoLocator().nearbyLocations();
}
When I call this above function, It prints null. But When I tried to print in the nearbyLocations() It print the data. But not when I Call loadData(). I'm going to use this returned data in listview.
You are mixing await, streams and then. This is probably too much to keep in mind at the same time.
Focus on one method at first. I suggest async/await since that is the easiest.
Your nearbyLocations method does not return anything. You did not define a type and it does not have a return statement either. Yet, you seem to expect it to return a Future<> with a specified type.
Make sure you crank up your warnings and use the pedantic package to have your analyzer notify you when you forget those things.
When you actually declare your method fully, your warnings should show you that you have no return in your method.
I don't have a compiler here or packages to include, but this seems to be what you really want:
Future<List<DocumentSnapshot>> nearbyLocations() async {
final collectionRefer = FirebaseFirestore.instance.collection('locations');
final radius = 10.0;
final field = 'position';
final center = await getCurrentLocation();
final stream = geo
.collection(collectionRef: collectionRefer)
.within(center: center, radius: radius, field: field, strictMode: true);
return stream.first;
}

how to return a stream list from a for loop in flutter

The code and comments kind of explains it better than I can articulate it.
basically I want to return a Stream of something but only based on certain parameters. Those parameters are coming from an array.
here is an example.
say we have an array with values ["1", "2", "3"]
and in the database I have a docids of ["1", "2","3","4"]
I want a stream that will return everything but that four, or to better articulate it. I want a stream list that will return only the items that have the docid of the array with those values specified aka [1,2,3]
what I did below was loop through the example array so the first item "c" will be "1".
it will take this "1" and use a where to see if a docid matches this "1". I need to store this somehow and then return it once it is "fullly" populated. or populated at all since it is a stream. The example array of [1,2,3] could change in the future to maybe [1,2,3,4] so when that happens I would like the data to be pulled from the database.
class UserData {
String uid;
String firstName;
int rating;
List<String> classes; //need to be able to access this
UserData.fromMap(Map<String, dynamic> data) {
firstName = data['firstname'] ?? "";
rating = data['rating'] ?? "";
classes = data['classes'].cast<String>() ?? "";
}
Stream<List<ClassData>> getTheUserClasses = (() async* {
List<ClassData> d = List<ClassData>();
for (String c in classes) { //no longer able to access classes
// this will store the data of the matched value
var _matchData = Firestore.instance
.collection("Classes")
.where('classid', isEqualTo: c)
.snapshots()
.map((event) => event.documents
.map((e) => ClassData.fromUserMap(e.data, e.documentID)));
// and when you have it, append
d.add(_matchData); //error here from type differences
}
// after the loop you can test your response then yield
d.forEach((item) => print(item));
// return the data which is required
yield d;
})();
UserData({this.firstName, this.rating, this.classes});
}
Here is a way I have already done this. The problem is that it won't refresh the widget tree when data is updated.
Future<void> getTheUserClasses() async {
List<ClassData> _classList = List<ClassData>();
for (String c in user.classes) {
DocumentSnapshot classsnapshot =
await Firestore.instance.collection("Classes").document(c).get();
final data =
ClassData.fromUserMap(classsnapshot.data, classsnapshot.documentID);
if (data != null) {
_classList.add(data);
}
}
notifyListeners();
classes = _classList;
}

Firestore Flutter How to get a list all the documents and its data inside a collection?

I have been working to get a list of all the documents inside a firestore collection. I want to display all details of all documents inside a collection.
My document tree is ask follows-
'groups' COLLECTION----->Documents w 'groupID' as reference------>'tasks' COLLECTION------>Documents w 'taskId' as reference.
Now I want to get all documents and its details inside 'tasks' collection for a particular groupID.
Future<MyTask> getCurrentTask(String groupId) async {
MyTask retVal = MyTask();
try {
DocumentSnapshot _docSnapshot =
await _firestore.collection("groups").document(groupId).collection("tasks").get();
retVal.taskId = taskId;
retVal.taskName = _docSnapshot.data['taskName'];
retVal.dueTime = _docSnapshot.data['dueTime'];
retVal.member =_docSnapshot.data['member'];
retVal.completed = _docSnapshot.data['completed'];
} catch (e) {
print(e);
}
return retVal;
}
I tried this but it doesnt work as "The method 'get' isn't defined for the type 'CollectionReference'."
How to get around this please?
Simply do like this:
Firestore.instance.collection("groups").document(groupID).collection("task").snapshots().listen((event) {
retVal = event.documents.map((e) => MyTask.fromJson(e.data)).toList();
});
I assume your MyTask model already have fromJson method so do it like that. And change your retVal to List: List<MyTask> retVal = [];. It will get all of your document and also listen whether there's change's on the collection.

Wrong order of execution in Flutter asynchronous code

I am having problems with futures in a Flutter app.
void saveCats() async {
var cats = await convertToCats(_rawData);
await DatabaseProvider.db.addCats(cats);
}
Future<List<Cat>> convertToCats(CatList catData) async {
var cats = <Cat>[];
await catData.forEach(key, value) async {
var pos = await calculatePos();
print('This should come first');
cats.add(Cat(name: key, pos: pos);
}
}
Future<int> calculatePos() async {
return await DatabaseProvider.db.calculatePos();
}
database.dart:
Future<void> addCats(List<Cat> cats) async {
print('This should come second');
// Do adding stuff here
}
Future<int> calculatePos() async {
// Some code to calculate the position
return pos;
}
In the above code, the saveCats function is called when a button is tapped. This function converts some raw data to a list of Cat models, and adds them to the database. As part of this conversion process, it calculates the pos of the cat in a list. My problem is that I would expect that, of my two print statements, the one in the forEach loop should come before the one in the addCats database function. But instead they appear in reverse order. Where am I going wrong?
You can't async/await in List.forEach() or Map.forEach() as both of them return void.
Either use
await Future.forEach([1, 2, 3], (num) async {
await asyncMethod(num);
});
or something similar;
https://api.flutter.dev/flutter/dart-async/Future/forEach.html
forEach often doesn't do what you expect, because the provided function runs as a closure.
It's more natural when you want to iterate over a list doing something to each element to use for (or one of the more functional type methods like map).
It's not clear what type CatList is, so this is approximate, but you'll want something more like:
Future<List<Cat>> convertToCats(CatList catData) async {
var cats = <Cat>[];
for (var i = 0; i < catData.length; i++) {
var key = catData[i].key;
var pos = await calculatePos();
print('This should come first');
cats.add(Cat(name: key, pos: pos));
}
return cats;
}
or
Future<List<Cat>> convertToCats(CatList catData) async {
return catData.map(...some mapping function...).toList();
}

Resources