Context
My app uses a similar functionality like Tinder:
- Get nearest users (geohash filter + some others)
- User can like/dislike users
- On subsequent launches, user needs to see NEW users, without those he or she already liked/disliked
Current structure looks like this:
This is my code so far:
UserRepository:
// Geohashing with GeoFlutterFire2
Stream<List<DocumentSnapshot>> getAllUsersInRadius(
{required LocationData currentLocation, required double radius}) {
String field = 'position';
GeoFirePoint centerPoint = _geo.point(
latitude: currentLocation.latitude ?? 0.0,
longitude: currentLocation.longitude ?? 0.0);
return _geo
.collection(collectionRef: _buildUserQuery())
.withinAsSingleStreamSubscription(
center: centerPoint, radius: radius, field: field);
}
// The query to pre-filter BEFORE the geohash query
Query<Map<String, dynamic>> _buildUserQuery() {
// Filter by 1. location (above), 2. isActive, 3. game list, 4. user type
var query = _firestoreDB
.collection(userCollection)
.where("isActive", isEqualTo: true)
.where("gameList", arrayContainsAny: ["Example1","Example2"])
.where(Filter.or(Filter("userType", isEqualTo: currentUser.lookingFor[0]),
Filter("userType", isEqualTo: currentUser.lookingFor[1])));
return query;
}
// UserRelations = list of liked/disliked other users
Future<List<String>> getUserRelationIDs(String userID) async {
List<String> result = [];
await _firestoreDB
.collection(userCollection)
.doc(userID)
.collection("user_relations")
.get()
.then((querySnapshot) {
for (var doc in querySnapshot.docs) {
final otherUserID = doc.data()["otherUserID"];
result.add(otherUserID);
}
});
return result;
}
UserViewModel:
Future<void> _getAllUsersInRadius() async {
currentLocationValue = await _userRepo.getCurrentUserLocation();
await _userRepo.getCurrentUser(_authRepo.user!.uid);
if (currentLocationValue != null) {
// Needed for later filtering
final userRelationIDs =
await _userRepo.getUserRelationIDs(_userRepo.currentUser!.uuid);
var subcription = _userRepo
.getAllUsersInRadius(
currentLocation: currentLocationValue!, radius: 2000)
.listen((documentList) {
// First, filter out all users that were already liked/disliked by currentUser
var filteredUserList = documentList.where((docSnapshot) {
if (!docSnapshot.exists || docSnapshot.data() == null) return false;
String uuid = docSnapshot.get("uuid");
if (!userRelationIDs.contains(uuid)) return true;
return false;
});
// Now turn all documents into user objects and publish the list
allUsersList = filteredUserList
.map((docSnapshot) => _createUserFromSnapshot(docSnapshot))
.toList();
_allUsersStreamcontroller.add(allUsersList);
});
subscriptions.add(subcription);
}
}
My Problem
I see the following points as problematic:
- It feels very inefficient to load all the users first and then possibly discard most of them -> wasted reads/month
- Due to using a geo-query (GeoFlutterFire2), I am very limited in the other filters I can apply (no ranges or sorting, since that is already used for the geohash) -> so I don't have a lot of options on the query side
So what I am wondering is: is there a more performant/efficient way to go about this?
Maybe by structuring the data differently?