Cloud Firestore QuerySnapshot Where function field not supported - flutter

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.

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

Firebase Sorting is not working properly in 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.

Flutter Firestore only return user overview ListTile when field contains specific words

I am listing users in a CustomScrollView/SliversList,ListTiles. I have a String field in my firestore and only want to return ListTile of a user, where his String field contains specific words (more than 2). For example, the users fields contain: "Apples, Ice, Bananas, Soup, Peaches, e.g...." and i want to list all users which have apples and bananas inside the field. how can i achieve this?
The only way to do it at the moment (with the way you have it set up) is actually pulling the value and doing a string "contains" or splitting the string into an array and check whether the value is within that array, otherwise I'd advise to refactor that field and make it into an array, that way you can perform a native arrayContainsAny against your field.
For you it will look like this (with your current implementation):
// ... after pulling all users' documents
// let's say your field is called 'foodField':
var criteria = 'Banana';
var fieldContent = doc.data()['foodField'];
// you can either do this:
if (fieldContent.toLowerCase().contains(criteria.toLowerCase())) {
// ...
}
// or you can tokenize it depending on your purposes...
var foodTokens = fieldContent.split(',').map((f) => f.toLowerCase());
if (foodTokens.contains(criteria.toLowerCase()) {
// ...
}
If your Firestore field was an array type, then you could've just done that, while querying:
FirebaseFirestore.instance.collection('users').where('foodField', arrayContainsAny: ['Banana', 'Apples'])
Which then would give you only the users whose foodField contain that value.
As you can see from previous questions on querying where text contains a substring, Firestore does not currently support such text searches. The typical solutions are to either perform part of your filtering in your application code as Roman answered, or to integrate a third-party full-text search solution.
In your specific case though, your string seems to be a list of words, so I'd recommend considering to change your data model to an array of the individual values in there:
"foodFields": ["Apples", "Ice", "Banana", "Soup", "Peaches"]
You can then use array field operators in the query.
While there is no array-contains-all operator, using array-contains you can at least filter on one value in the database, and with array-contains-any you can do on OR like condition.
Another data model would be to store the individual values in a map field with value true for each of them:
"foodFields": {
"Apples": true,
"Ice": true,
"Banana": true,
"Soup": true,
"Peaches": true
}
With such a structure you can perform an AND like query with:
collectionRef
.where('foodFields.Apples', isEqualTo: true)
.where('foodFields.Bananas', isEqualTo: true)

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

How to add a timestamp to my array/List in firestore?

I wish to generate a scatter plot using time on the x-axis from user generated submissions. To do so I am making a list of maps in which each map is the user submission and contains a timestamp field. The issue is that I cannot add a timestamp using the arrayUnion. This issue was mentioned in this post:
Link1
You can't use FieldValue.serverTimestamp() as the value to union (add) or >remove, to or from, an array type value of a document field. If you want to >use that timestamp value, you need to pass it directly to a field you're >updating or setting.
So,to solve my problem I need to update directly the field for timestamp after I have already added some data which has a form similar to below and I only need to add to the last element/Map of my List/array of Maps but I do not know how long this list currently is:
myList[{Map1}{Map2}{Map3}...{examplefield:'Some data', timstamp:null}]
final Map submissionMap = submissionData.toMap();
//Running a firestore transaction...
final DocumentReference areaDocument = Firestore.instance.document('Space/SAJJEED');
Firestore.instance.runTransaction((Transaction tx) async {
DocumentSnapshot postSnapshot = await tx.get(areaDocument);
if (postSnapshot.exists) {
//Adds the data except for the timestamp needed
await tx.update(areaDocument, <String, dynamic>{'$status': FieldValue.arrayUnion([submissionMap])});
//Insert Code for directly accessing the timestamp field and adding Timestamp
}
});```
Any other workarounds would be appreciated as well.