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

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)

Related

Return documents based on field bitwise AND operator

I have a model which has a field 'permissions', and I would like to retrieve all the documents from my table whose field 'permissions' meets a certain condition based on a bitwise AND operator. For example, what I am doing currently is the following in my typescript codebase:
let users: User[] = await this.userModel.find();
users = users.filter( // I would like to get rid of this filter (do the filtering directly through mongo)
(user) => user.permissions & 2 > 0 // bitwise AND operator
);
However, I would like to do the operation directly through mongo, so I don't need to get all the users inside my table and then filter them out from my typescript code.

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

mgo $all query an array with an array and be case insensitive?

I have an array of ingredient names that is dynamic and provided per user. I'd like to match it to mongo documents where there is an array of objects called ingredients which has a property name. I've written a query (see below) which will take query parameters from the URL and will return all the documents that have all matching ingredient names, however this search is case sensitive and I'd like it not to be.
I've considered using bson.RegEx with Option: "i", however I'm not sure how to form this query or apply it to an array of strings.
Here is the case sensitive query:
// Check for ingredients, return all recipes that can be made using supplied ingredients
if qryPrms["ingredients"] != nil {
mongodQ["ingredients.name"] = bson.M{"$all": qryPrms["ingredients"]}
}
mongodQ is the bson.M I use to query the collection later in the code. Ideally I could apply RegEx to each element in qryPrms["ingredients"] so it would return closely matching ingredients like cheese would return swiss cheese as well. This is also a more general mongodb question I suppose when it comes to querying with a dynamic array.
I was able to accomplish this using a for loop to build a slice of type bson.RegEx.
if qryPrms["ingredients"] != nil {
var ingRegEx []bson.RegEx
for i := range qryPrms["ingredients"] {
ingRegEx = append(ingRegEx, bson.RegEx{Pattern: qryPrms["ingredients"][i], Options: "i"})
}
mongodQ["ingredients.name"] = bson.M{"$all": ingRegEx}
}

Search Array with depending order

I got a question regarding filtering an array.
Let's assume I got an array of country names:
let countries = [Albania, Bahrain, Barbados, Denmark, France, Zimbabwe]
now I want to filter this array to check if it contains a certain String let say "ba".
I can do this easily with
countries = countries.filter{ $0.contains("ba")}
which returns
Albania, Bahrain, Barbados, Zimbabwe
But I actually want the order of letters to matter. Therefore, the result "Albania" and "Zimbabwe" should not appear and only "Bahrain" and "Barbados" as their name starts with an Ba.
Is there any way to do this to avoid a huge for loop going through all entites checking individually for each character?
Use .hasPrefix instead of .contains, like this:
print(countries.filter{ $0.hasPrefix("Ba") })
Note that this is case sensitive. BTW, in your example, the problem was not missing order of letters but the fact that .contains respects case as do most methods in swift.
You can take care of the case and the filtering in one step:
let countries = ["Albania", "Bahrain", "Barbados", "Denmark", "France", "Zimbabwe"]
let filtered = countries.filter { $0.lowercased().hasPrefix("ba") } // -> ["Bahrain", "Barbados"]
This lowercases the country names before applying the filter's test, but doesn't change the original array, so the results have the same case.
This is important because you might incorrectly want to do this in two steps for readability:
countries
.map { $0.lowercased()}
.filter { $0.hasPrefix("ba")}
But this returns ["bahrain", "barbados"] because the filter is being applied to the now lowercased array.

Composite views in couchbase

I'm new to Couchbase and am struggling to get a composite index to do what I want it to. The use-case is this:
I have a set of "Enumerations" being stored as documents
Each has a "last_updated" field which -- as you may have guessed -- stores the last time that the field was updated
I want to be able to show only those enumerations which have been updated since some given date but still sort the list by the name of the enumeration
I've created a Couchbase View like this:
function (doc, meta) {
var time_array;
if (doc.doc_type === "enum") {
if (doc.last_updated) {
time_array = doc.last_updated.split(/[- :]/);
} else {
time_array = [0,0,0,0,0,0];
}
for(var i=0; i<time_array.length; i++) { time_array[i] = parseInt(time_array[i], 10); }
time_array.unshift(meta.id);
emit(time_array, null);
}
}
I have one record that doesn't have the last_updated field set and therefore has it's time fields are all set to zero. I thought as a first test I could filter out that result and I put in the following:
startkey = ["a",2012,0,0,0,0,0]
endkey = ["Z",2014,0,0,0,0,0]
While the list is sorted by the 'id' it isn't filtering anything! Can anyone tell me what I'm doing wrong? Is there a better composite view to achieve these results?
In couchbase when you query view by startkey - endkey you're unable to filter results by 2 or more properties. Couchbase has only one index, so it will filter your results only by first param. So your query will be identical to query with:
startkey = ["a"]
endkey = ["Z"]
Here is a link to complete answer by Filipe Manana why it can't be filtered by those dates.
Here is a quote from it:
For composite keys (arrays), elements are compared from left to right and comparison finishes as soon as a element is different from the corresponding element in the other key (same as what happens when comparing strings à la memcmp() or strcmp()).
So if you want to have a view that filters by date, date array should go first in composite key.