How to check if data matches field in array in flutter - 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

Related

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)

How to arrange documents in Firestore using Flutter through custom document IDs?

I want to order the documents in Firestore. The default Firestore documents list consist of alphabetic characters which get created automatically. But I don't want that. I just want to see my newly added document added at the top of my documents list. How do I do that in flutter? It would be very helpful if you provide me with a code for that. The code I use to create a collection is:
Future<void> userSetup() async {
String user = FirebaseAuth.instance.currentUser?.displayName as String;
CollectionReference users = FirebaseFirestore.instance.collection(user);
final hours = time?.hour.toString().padLeft(2, '0');
final minutes = time?.minute.toString().padLeft(2, '0');
users.add({
"customerId": FirebaseAuth.instance.currentUser?.uid.toString(),
"customerName": FirebaseAuth.instance.currentUser?.displayName,
"customerEmail": FirebaseAuth.instance.currentUser?.email,
"selectedTime": '${hours}:${minutes}',
"selectedDate": DateFormat('dd/MM/yyyy').format(date!),
});
return;
}
But I am unable to set my own document id. Please help me with the issue. Thanks in Advance
From the Flutterfire documentation, the set() method is the one you should be using to be able to specify your own document IDs instead of add(). Keep in mind that if the document ID you specify already exists in your database, the whole existing document will be replaced. This is a sample usage as found in the documentation:
CollectionReference users = FirebaseFirestore.instance.collection('users');
Future<void> addUser() {
return users
.doc('ABC123')
.set({
'full_name': "Mary Jane",
'age': 18
})
.then((value) => print("User Added"))
.catchError((error) => print("Failed to add user: $error"));
}
It seems that documents are ordered alphabetically in the Firestore console, so your custom document IDs should follow alphabetical order as you require. Not to be confused with retrieving documents from Firestore in a particular order, which is done with the orderBy() method.

How to approach dynamically generated Firestore queries depending on nested user created Sub Collections?

/Countries/Lebanon/Governorates/Mount Lebanon/Districts/Chouf/Cities/Wadi al-Zayneh/Data/Products/Main Categories/Restaurants & Bakeries/Sub Categories/Snack/Sub Categories/Abo Arab Cafe
So as you can see, this is a snippet from my current Firestore structure. So many deeply nested collections. The issue is, I want to keep going deeper as long as a collection called 'Sub Categories' is found which in that case I would render them in the UI. And when eventually I reach a level where 'Sub Categories' is not found, I will render a different UI and show the actual products (The last document "Abo Arab Cafe" contains all the products as maps). The pattern of how many Sub Categories there are is unexpectable and can be modified by the end user.
How can I keep checking for Sub Categories? How to manage my queries in a way that they are dynamically generated at each level at the client-side?
I use Flutter. Here is my current queries structure:
import 'package:cloud_firestore/cloud_firestore.dart';
class FirebaseServices {
final FirebaseFirestore _db = FirebaseFirestore.instance;
CollectionReference mainCategoryCollectionReference() {
CollectionReference mainCategoryCollectionReference = _db.collection(
'/Countries/Lebanon/Governorates/Mount Lebanon/Districts/Chouf/Cities/Wadi al-Zayneh/Data/Products/Main Categories');
return mainCategoryCollectionReference;
}
CollectionReference subCategoryCollectionReference(
String parentSelectedCategory) {
CollectionReference mainCategoryCollectionReference = _db.collection(
'/Countries/Lebanon/Governorates/Mount Lebanon/Districts/Chouf/Cities/Wadi al-Zayneh/Data/Products/Main Categories/$parentSelectedCategory/Sub Categories');
return mainCategoryCollectionReference;
}
bool checkIfSubCategoriesExist(CollectionReference collectionReference) {
bool subCategoriesExist;
collectionReference.get().then((value) => {
subCategoriesExist = value.docs.isNotEmpty,
print('SubCategoriesExist: $subCategoriesExist')
});
return subCategoriesExist;
}
}
This works only if I know for certain how many levels of deepness there are, but since this can be modified by the user, it won't work.
Sorry for the very long question I had no idea how to explain it properly and clearly. Thank you in advance!
The structure is all wrong, there is no point in the structure being this deeply nested. The structure of the database needs to match what has to appear in the UI.
Assuming this is a worldwide application since you are using countries then you have to do the following:
Collection
Document
Fields
Countries
Random ID
countryName - arrayOfDistrict- arrayOfGovernorates
3 Fields under each document id, containing information about the country.
Then regarding Resturants:
Collection
Document
Fields
SubCollection
subCollectionId
Fields
Resturant
Random ID
resturant_name- resturant_location - info_about_resturant
Menu
randomId
dish_name - price -...
The problem with your db structure is that it is very nested instead of making a flat structure and that right now you are harcoding the whole path.
Using the above structure, you can create a dropdown with list of countries if the user chooses Lebanon, then you get the districts and the governorates. Then you can do a call to get the resturants that are inside each district, since in the documents inside Resturant collection you can get location of each resturant and name.
After that on click of each resturant, you will get the data inside the subcollection that will contain the full menu.
I think I found the solution with the help of a friend!
Since the checkIfSubCategoriesExist function is always checking on the very last reached level(using the collectionReference argument) whether Sub Categories exists or not, he suggested that in case it does exist, I can append to its argument collectionReference the new "Sub Categories" String to the path as a variable! This way I can query on it and voila!

Querying Firestore map fields in flutter

I am trying to retrieve posts in my Firestore database from a field called "bookmarks". The field "bookmarks" is of type map. When a user bookmarks a post, the field is updated with the deviceId being set to true like this:
▼ bookmarks
deviceId : true
When the post is unbookmarked, deviceId : trueis deleted and the bookmarks field remains empty like this:
bookmarks: {}
I am trying to retrieve posts from "bookmarks" where deviceID is set to true. My query however returns all posts including posts where bookmarks is empty.
This is my query :
QuerySnapshot snapshot = await postsRef
.document(postId)
.collection('users_posts')
.where('bookmarks', arrayContains: deviceId)
.orderBy('timestamp', descending: true)
.getDocuments();
Please help figure out what I am doing wrong. Attatched is the structure of my database.
Thank you.
Your bookmarks field is not an array, so you can't query it like an array. It's a map, and you can query for the values of nested fields of a map using dot notation:
.where('bookmarks.${deviceId}', isEqualTo: true)
I tried that notation but didn't work for me, here is the notation I used:
where("bookmarks.deviceId:true"), you can replace true with the value you are looking.

Firestore + Swift Query - Check for value within an array in specified document

Is there a way to check for value within an array if you're querying for a specific document ID?
This doesn't work:
let userID = Auth.auth().currentUser?.uid ?? "nil"
let db = Firestore.firestore()
db.collection("users").document(userID).whereField("favorites", arrayContains: productID)
Seems like you can only use .whereField directly after declaring the collection, but not on the document.
The idea of the where methods is to find docs in a collection satisfying some criterion.
If you've already got a doc ref, just get() it and check the array in the client.
Alternatively, query the collection with where, and see if your user id is one of the ids in the resulting snapshot.