Firebase Sorting is not working properly in flutter - flutter

I'm developing a course app in flutter. so there is a subcollection for displaying episodes inside the course. and I'm sending episode numbers when creating the episodes to sort the episode with the value using the firebase orderBy function.
it's working but not ordering properly. (what I mean by working is it's sorting differently but messed up).
here are all codes and details
Read Episode code
#override
Stream<List<EpisodesModel>> readEpisode({
required id,
}) {
return FirebaseFirestore.instance
.collection('courses')
.doc(id)
.collection('eps')
.orderBy('episodeNum', descending: false) // the eps num is the number that I send to firebase db when creating course
.snapshots()
.map((snapshot) => snapshot.docs
.map((doc) => EpisodesModel.fromJson(doc.data()))
.toList());
}
but this is how the value is sorting
please comment if any other code is needed,

It looks like the values in your episodeNum field are stored as string values, and not as numbers. And in the lexicographical ordering (which Firestore uses for string values) "10" comes before "2", just as "aa" comes before "b".
To fix the problem, store the episodeNum values as numbers in the database, and Firestore will order them correctly.

Related

How to check if data matches field in array in flutter

I have a Firebase Firestore configuration as show below:
How would I check if eventParticipants's contains a uid matching the current user's uid? The below code used to work when eventParticipants used to be an array of user id's, but since creating more detailed array objects, the code seems to not work:
data["eventParticipants"]
.contains({
"uid": FirebaseAuth
.instance.currentUser!.uid
.toString()
})
Usually the above code would return a bool, and I would use the result in a ternary operator to load a widget, however, I am unable to rework the logic with the new array structure. Subsequently, how would I remove an array object if it's uid field matches the current user's id?
FirebaseFirestore
.instance
.collection(
"events")
.doc(document.id)
.set(
{
"eventParticipants":
FieldValue
.arrayRemove([
{
"uid": FirebaseAuth
.instance
.currentUser
?.uid
}
])
},
SetOptions(
merge: true),
);
Any pointers would be appreciated!
The arrayRemove (and arrayUnion and arrayContains) operators expect you to pass the complete, exact contents of the item in the array. In your case it looks for an item in the array with a single field uid with the value you pass.
So unless you know the values of all properties of the array item that you want to remove, you'll have to:
Read the document with the array in it.
Manipulate the array in your application code.
Write the entire array back to the database.
Also see:
Firestore, how to structure a "likedBy" query
Firestore conditional array query
Firestore array-contains-any is not working properly

How to filter firebase data using a where clause with FieldPat.documentId

Im trying to retrieve data with
Stream<List<User>> getUsers(User user) {
List<String> userFilter = List.from(user.swipeLeft!)
..addAll(user.swipeRight!)
..add(user.uid!);
return _firebaseFirestore
.collection('users')
.where('interestedIn', isEqualTo: 'HIRING')
.where(FieldPath.documentId, whereNotIn: userFilter)
.snapshots()
.map((snap) {
return snap.docs.map((doc) => User.fromSnapshot(doc)).toList();
});
}
I get an error
An error occurred while parsing query arguments, this is most likely an error with this SDK.
Invalid query. When querying with FieldPath.documentId() you must provide a valid document ID, but it was an empty string.
My data in firebase is structured as follows
How can I fix this?
In firebase you cannot filter with the document id, you can only filter with the fields in the document, a way a simple solution will be to generate a unique id by your self using packages like uuid and then save the document with the id and also save the id inside the document fields then you can filter with the id in the document field

Cloud Firestore QuerySnapshot Where function field not supported

I am using Cloud Firebase on my android app and I want to filter my documents using where function to avoid unnecessary billing cost, my problem is instead of using document field inside my where function I prefer model to filter my data due to multiple advantage, then I try below code, but it trough error of
"'field is String || field is FieldPath || field == FieldPath.documentId': Supported [field] types are [String] and [FieldPath].,"
I try this
final authPhoneNo = Utils.formatPhoneNo(phoneNo!);
CollectionReference receiptCollection = FirebaseFirestore.instance
.collection('example');
QuerySnapshot querySnapshot;
querySnapshot = await receiptCollection.orderBy('Date', descending: true).where((e){
Utils.formatPhoneNo(MainData.fromJson(e.data()).userPhoneNo!);
}, isEqualTo: authPhoneNo).get();
formatPhoneNo
static formatPhoneNo(String phoneNo) => phoneNo.replaceAll(RegExp(r"\D"), "")
.substring(
(phoneNo.replaceAll(RegExp(r"\D"), "").length) - 9,
(phoneNo.replaceAll(RegExp(r"\D"), "").length)
);
It through error on where((e)
'field is String || field is FieldPath || field == FieldPath.documentId': Supported [field] types are [String] and [FieldPath].
From the code you provided, I see that you want to filter data using where and a formatter function formatPhoneNo, but this is not how it works according to the official documentation:
// Create a reference to the cities collection
final citiesRef = db.collection("cities");
// Create a query against the collection.
final query = citiesRef.where("state", isEqualTo: "CA");
As you can see, the first parameter on the where clause refers to the field you want to filter and the second one refers to the query operation you want to perform (in this case, isEqualTo) and the reference value (in the example, the string "CA").
In your case, the right way to do the query would be as follows:
querySnapshot = await receiptCollection.where('phoneNo', isEqualTo: authPhoneNo).get();
To avoid the use of a formatter, which leads to unnecessary processing time every time it is called and; in this case could lead to format every result until the desired value matches the condition if its done without the where Firebase function, I would recommend to sanitize your data before saving it to Firebase as recommended in this article:
2. Standardize phone number formatting
When you capture telephone data, either directly from the customer or via an ingress from a data supplier, you should standardize the telephone number format. Ideally, you should use the industry-standard E.164 format
...
If you prefer to display a telephone number in a nice to read human format you can still store the number in your database in E.164 format but present the number on the screen in a friendly format.
Doing this way, you could filter your data saved in Firebase using simple and compound queries as shown in the Firebase documentation.

Using fields in a document for another query

I have 2 collections, one called Timeline and one called Posts. The first one is very simple, having 2 fields: 'PostId' and 'OwnerId', while the second one is a little bit more complex but it is not important for the purpose of my question.
Using 'OwnerId' and 'PostId' I can get a specified post in the collection Posts.
What I want to do is getting all the docs in timeline of a specified user, for each doc use it to get the post infos in Posts collection, and order the posts in descending timestamp, but I can't find a smart and effective way to do so.
To get all the docs of a specified user in Timeline I write:
QuerySnapshot snapshot = await timelineRef
.doc(currentUserID)
.collection('timelinePosts')
.get();
And to get a specified post from Posts collection I write:
QuerySnapshot snapshot = await postsRef
.doc(ownerId)
.collection('userPosts')
.doc(postId)
.get();
How can I mix these two to get the result I want? Thank you
There is no concept of a server-side join in Firestore, nor is there a way to filter the documents returned based on information in documents in another collection. All Firestore queries can do is evaluate the literal data in the candidate documents (through an index) and filter based on that.
So you will either have to duplicate the data to filter on in each userPosts document, or perform a so-called client-side join - with the latter being the most reasonable option for this use-case as far as I can see.
You'll end up with individual get() calls for the documents, or a bunch in in queries on the FieldPath.documentId() you get from timelinePosts, and then merge the results in your application code.
At the moment I found a solution that is not very elegant but at least is working:
QuerySnapshot snapshot = await timelineRef
.doc(widget.currentUser.userID)
.collection('timelinePosts')
.orderBy('timestamp', descending: true)
.get();
List<TimelineItem> timelineItems =
snapshot.docs.map((doc) => TimelineItem.fromDocument(doc)).toList();
List<PostWidget> postsTemp = [];
for (var element in timelineItems) {
DocumentSnapshot documentSnapshot = await postsRef
.doc(element.ownerId)
.collection('userPosts')
.doc(element.postId)
.get();
postsTemp.add(PostWidget(Post.fromDocument(documentSnapshot)));
}
I added timestamp field to my timelinePosts, created a class to contain the data from the first query, and then I did a second query based on the parameters I got on the first one for each doc.
Hopefully I'll find a more efficient solution but at the moment I use this

DocumentID search in Firestore with a List

Can i search a Firestore DocumentID with a List<String>?
I am trying to search through my collection with some selection of documentID in a List. The List will consist of few String. Can I search through the Firestore collection using this?
This is the List:
List<String> _selectedBusStop = List<String>();
This is the code I used in finding the DocumentID based on the list that is in here.
Future <void> saveRoute(_selectedBusStop) async{
Firestore.instance.collection('markers').where('BusstopName', isEqualTo: _selectedBusStop)
.snapshots().listen((location) {
if(location.documents.isNotEmpty){
for (int i = 0; i < location.documents.length; i++){
initRoute(location.documents[i].data, location.documents[i]);
}
}
});
setState(() {
});
}
I am using where and isEqualTo or is this approach wrong? Any idea how to make it work for this part? Thank you in advance for your help.
Update:
This is how my Firestore looks like:
The List have some of the BusstopName but not all of it. I do not want to retrieve all the data from the Firestore just the one that is in the List. Sorry for causing so many misunderstanding.
Use the whereIn operator, like this:
Future <void> saveRoute(_selectedBusStop) async{
Firestore.instance.collection('markers').where('BusstopName', whereIn: _selectedBusStop)
.snapshots().listen((location) {
if(location.documents.isNotEmpty){
for (int i = 0; i < location.documents.length; i++){
initRoute(location.documents[i].data, location.documents[i]);
}
}
});
setState(() {
});
}
Assuming your documents have a unique id stored in the field BusstopName and also the documents actual id matches the content of this field, you have 2 possibilities.
(1) .where query
query data with collection("markers").where("BusstopName", "=", "yourBuststopId").
this returns a querySnapshot Object, on which you can call .size to check if there were any documents with that Id found (could be more than 1 if you have an inconsistent database).
(2) .doc query
query data with collection("markers").doc("yourBuststopId")
this returns a documentSnapshot Object, on which you can call .exist to check if the document actually exsists.
In both cases you need to do 1 query per Id, because Firestore queries only support equality and range operations. See this similar SO question. I would suggest to do the queries asynchronously, otherwise the time to execute will increase with the size of the array.
If you are concerned about costs, you only get billed for the results that actually return documents that exist.
you might also try this:
FirebaseFirestore.instance
.collection('markers')
.where('BusstopName', arrayContainsAny: ['Utar Bus Stop', 'Garden Bus Stop'])
.get()
.then(...);
Taken from the examples documentation