I'm writing a Flutter application which uses the user's location to identify items around them. As it's only possible to do comparison queries against one field in Firebase, I'm using geohashes as per Frank van Puffelen's lecture on the subject.
I've got the initial query working fine:
qs = await Firestore.instance
.collection('users')
.where('geohash', isGreaterThanOrEqualTo: boundaryGeoHash1)
.where('geohash', isLessThanOrEqualTo: boundaryGeoHash2)
.getDocuments();
this returns all the people in the required geographic area, as expected. The problem is, I expect there to be a high volume of users, so I want to limit the amount of downloads per query to a certain number - say 10.
When I write this query (which I believed should do the trick), what actually seems to happen is that the table is ordered by 'geohash', it then queries the first 10 items in the table, and then sends me the records in the first 10 items that fulfil the requirements of the .where part of the query:
qs = await Firestore.instance
.collection('users')
.orderBy('geohash')
.where('geohash', isGreaterThanOrEqualTo: boundaryGeoHash1)
.where('geohash', isLessThanOrEqualTo: boundaryGeoHash2)
.startAfterDocument(dS)
.limit(10)
.getDocuments();
//dS = last document from previous query
whereas what I want the query to do is to always send me 10 results, all of which fulfil the .where part of the query.
Using the test database I've generated, calling the initial query returns 114 results. The second one finds 2 which brings me to my conclusion as to what is happening.
Can what I'm trying to do be achieved? And if so, any clues as to what I'm doing wrong?
EDIT
As per Frank's request, dS is populated as follows:
if (qs != null) {
dS = qs.documents[qs.documents.length - 1];
}
//dS is a global variable
EDIT 2
Here is the function which does the query on the database.
Future<List<CutDownUserProfile>> getReducedUserDataBetweenGeohashes(
String boundaryGeoHash1, String boundaryGeoHash2, DocumentSnapshot dS, int downloadLimit) async {
List<CutDownUserProfile> retn = List<CutDownUserProfile>();
QuerySnapshot qs;
if (dS != null) {
print('ds != null');
print('dS.userNumber = ' + dS.data['userNumber'].toString());
print('dS.userID = ' + dS.data['userID']);
print('dS.geohash = ' + dS.data['geohash']);
print('dS.name = ' + dS.data['name']);
qs = await Firestore.instance
.collection('userHeaders')
.orderBy('geohash')
.where('geohash', isGreaterThanOrEqualTo: boundaryGeoHash1)
.where('geohash', isLessThanOrEqualTo: boundaryGeoHash2)
.startAfterDocument(dS)
.limit(downloadLimit)
.getDocuments();
} else {
print('ds == null');
qs = await Firestore.instance
.collection('userHeaders')
.orderBy('geohash')
.where('geohash', isGreaterThanOrEqualTo: boundaryGeoHash1)
.where('geohash', isLessThanOrEqualTo: boundaryGeoHash2)
.limit(downloadLimit)
.getDocuments();
}
if (qs.documents.isNotEmpty) {
print('through this! qs len = ' + qs.documents.length.toString());
List<DocumentSnapshot> ds = qs.documents.toList();
print('/////DS Len = ' + ds.length.toString());
for (int i = 0; i < ds.length; i++) {
CutDownUserProfile cDUP = CutDownUserProfile();
cDUP.geoHash = ds[i].data['geohash'];
Vector2 latLon = globals.decodeGeohash(cDUP.geoHash);
double dist = getDistanceFromLatLonInKm(globals.localUser.homeLat, globals.localUser.homeLon, latLon.x, latLon.y);
if (dist <= globals.localUser.maxDistance && dist <= ds[i].data['maxDist']) {
CutDownUserProfile cDUP2 = CutDownUserProfile();
cDUP2.userNumber = ds[i].data['userNumber'];
cDUP2.userID = ds[i].data['userID'];
cDUP2.geoHash = ds[i].data['geohash'];
retn.add(cDUP2);
}
}
if (qs != null) {
globals.lastProfileSeen = qs.documents[qs.documents.length - 1];
}
} else {
print('no results');
}
}
The line print('/////DS Len = ' + ds.length.toString());
prints out the length of the queries returned from the search, and, as mentioned earlier returns 2. Without the .orderBy
the .startAfterDocument(dS)
and the .limit(downloadLimit)
the code returns 114 results, which is what I expected. For completeness, here is the original code:
Future<List<CutDownUserProfile>> getReducedUserDataBetweenGeohashesALL(String boundaryGeoHash1, String boundaryGeoHash2) async {
List<CutDownUserProfile> retn = List<CutDownUserProfile>();
await Firestore.instance
.collection('userHeaders')
.where('geohash', isGreaterThanOrEqualTo: boundaryGeoHash1)
.where('geohash', isLessThanOrEqualTo: boundaryGeoHash2)
.getDocuments()
.then((event) {
if (event.documents.isNotEmpty) {
List<DocumentSnapshot> ds = event.documents.toList(); //if it is a single document
print('/////DS Len = ' + ds.length.toString());
for (int i = 0; i < ds.length; i++) {
CutDownUserProfile cDUP = CutDownUserProfile();
cDUP.geoHash = ds[i].data['geohash'];
Vector2 latLon = globals.decodeGeohash(cDUP.geoHash);
double dist = getDistanceFromLatLonInKm(globals.localUser.homeLat, globals.localUser.homeLon, latLon.x, latLon.y);
CutDownUserProfile cDUP2 = CutDownUserProfile();
cDUP2.userNumber = ds[i].data['userNumber'];
cDUP2.userID = ds[i].data['userID'];
cDUP2.geoHash = ds[i].data['geohash'];
retn.add(cDUP2);
}
} else {
print('no results');
}
}).catchError((e) => print("error fetching data: $e"));