Android Studio: Query documents containing array node using a substring - android

I am trying to search Documents in a Collection which contain a certain key.
Here is how I structured my Firestore database:
-- FirestoreRoot
|-- Products (Collection)
|-- Departments (Document)
|-- Food (Colletion)
|-- {Id} (Document)
-- description : "this is my very first description"
-- keywords :
-- 0 : this
-- 1 : is
-- 2 : my
-- 3 : very
-- 4 : first
-- 5 : description
In the example below, I was able to search by using a substring for the first word in the description. To this date, this method does not work for querying the following words. In the example below, typing the letters "thi" is enough to return documents.
CollectionReference colecRef = FirebaseFirestore.getInstance()
.collection("Products")
.document("Departments")
.collection("Food");
Query query = colecRef;
query.whereGreaterThanOrEqualTo("description", searchField.getText().toString().toLowerCase())
.whereLessThan("description", searchField.getText().toString().toLowerCase()+'\uf8ff')
.get().addOnSuccessListener(SearchActivity.this, new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
for (QueryDocumentSnapshot documentSnapshot : queryDocumentSnapshots) {
Log.i(TAG, "onSuccess: "+documentSnapshot);
}
}
});
In my case, I need to search by using 1 or multiple keys, for example: first or first description. And by using the whole word or only a substring, for example: first or descr
I've tried to use whereArrayContains(), but I can't use it typing multiple keys or substring.
query.whereArrayContains("keywords",searchField.getText().toString().toLowerCase())
.get().addOnSuccessListener(SearchActivity.this, new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
for (QueryDocumentSnapshot documentSnapshot : queryDocumentSnapshots) {
Log.i(TAG, "onSuccess: "+documentSnapshot);
}
}
})
I appreciate any help.

Firestore currently only offers two ways to query the contents of an array:
Array membership with whereArrayContains(). This will tell you if an element exists in an array. It must match exactly - no substrings.
array-contains-any memberhip with whereArrayContainsAny(). This will tell you if any of the given strings exist in the array. It must match exactly - no substrings.
As you can see, searching substrings in arrays isn't going to work at all. You might want to consider using another database in tandem with Firestore in order to satisfy these specific queries, as Firestore is not very well suited for them.

Related

Android Firestore query get the id of the document that contains the value in the search

Firestore database image
Hello, I just tried to use Firestore. I had some problem when getting document id.
The question is, I want to get a document id (red box) which has value (blue box) in it.
I use the following query:
collection("mychannel").whereEqualTo("74wRU4xHrcV9oWAXEkKeRNp41c53")
But did not give results.
Thanks!
As in the official documentation:
Although Cloud Firestore can store arrays, it does not support querying array members or updating single array elements.
So there is no way in which you can use the following query:
collection("mychannel").whereEqualTo("74wRU4xHrcV9oWAXEkKeRNp41c53")
If you only want to get the entire userId array you need to iterate over a Map like this:
collection("mychannel").document("1fReXb8pgQvJzFdzpkSy").get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
Map<String, Object> map = document.getData();
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getKey().equals("userId")) {
Log.d("TAG", entry.getValue().toString());
}
}
}
}
}
});
But note, even if userId object is stored in the database as an array, entry.getValue() returns an ArrayList, not an array.
So the output will be:
[74wRU4xHrcV9oWAXEkKeRNp41c53]
A better approach will be if you consider this alternative database structure, where each user id is the key in a map and all values are true:
userId: {
"74wRU4xHrcV9oWAXEkKeRNp41c53": true,
"AdwF...": true,
"YsHs...": true
}
This question is answered here: Firestore: Query by item in array of document
In summary, don't use arrays to store data in Firestore as the query you are trying to do is not available yet (remember it is still in beta). You should use a Map instead.

Android Firestore, Query Array

I want to query the highlighted values from the above database
My code is incomplete I can't figure how to complete the query.
I want to know if 123,456,789 exists anywhere in the entire collection,
like for a normal query .whereEqualTo("address","P18/A CIT Road Ananda Palit"); would give me the third document,here I want to query card
CollectionReference ref = db.collection("company");
Query query = ref.whereEqualTo("card[]","???");
To solve this, please use the following code:
ref.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
for (DocumentSnapshot document : task.getResult()) {
List<String> list = (List<String>) document.get("card");
for (String item : list) {
Log.d("TAG", item);
}
}
}
});
The output will be:
123
456
789
//and so on
Remember, document.get("card") does not return an array, it returns an ArraList.
If you want use a Query using whereEqualTo() method then I suggest you change your database a little bit. Your database structure shoould look like this:
Firestore-root
|
--- company
|
--- companyDocumentId
|
--- address: "P18/A CIT Road Palit"
|
--- card
|
--- 123: true
|
--- 456: true
|
--- 789: true
In this case the your query should look like this:
Query = ref.whereEqualTo("card.123", true);
Edit:
According to your commend the query should be:
Query = ref.whereEqualTo("card.E20040806709002620760CE82", true);
Edit 13 Aug 2018:
According to the updated documentation regarding array membership, now it is possible to filter data based on array values using whereArrayContains() method. A simple example would be:
CollectionReference citiesRef = db.collection("cities");
citiesRef.whereArrayContains("regions", "west_coast");
This query returns every city document where the regions field is an array that contains west_coast. If the array has multiple instances of the value you query on, the document is included in the results only once.

How can i read ArrayLists within a document from Firestore cloud database?

I'm Trying to build a restaurant app using firestore to store the orders and the users.
I tried 2 methods, first one was to write the ArrayList of orders as a Array in firestore database, but coudn't read them afterwards...
I used DocumentSnapshot.toObject(myclassoflist.class) but this only worked with no array list in the document, only with values = ".."
Then I created a collection of documents (each document is an item) which contains the array as simple values
To understand this take a look at my database
Then, to read them i first get all the document ids
db.collection("orders").get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (DocumentSnapshot document : task.getResult()) {
documentsIDs.add(document.getId());
}
Integer allIdsD = task.getResult().size();
if (allIdsD.equals(documentsIDs.size())) {
readDocs();
}
}
}
});
Then for each document id, i created 3 more db.collection(collection).get() in order to get the inside of each document within the subcollection, using again the function DocumentSnapshot.toObject(myclass.class).
The problem here is that it takes ~0.8 secs to get a complete order from the database, which is a lot considering there could be like 100+ orders per day
My project on GITHUB
Examples from: LimatexMM/app/src/main/java/g3org3/limatexmm/orders.java
EDIT:
I also tried to write the orders as follow:
orderListBig docData = new orderListBig(list, currentUser, adList, docId);
(list is ArrayList, currentUser, adList are objects)
db.collection("orderss").document(docId).set(docData)
and then read it with:
db.collection("orderss").get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (DocumentSnapshot document : task.getResult()) {
orderListBig ahah = document.toObject(orderListBig.class);
allOrders.add(ahah); (ArrayList of ordersListBig)
an part of my order document
As per official documentation regarding the use of arrays in Cloud Firestore you need to know that:
Although Cloud Firestore can store arrays, it does not support querying array members or updating single array elements.
If you only want to get the entire orderList array and get the value of, let's say itemMore, you need to get the reference of that particular document and then iterate over a Map like this:
Map<String, Object> map = documentSnapshot.getData();
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getKey().equals("itemMore")) {
Log.d("TAG", entry.getValue().toString());
}
}
But note, even if orderList object is stored in the database as an array, entry.getValue() returns an ArrayList, not an array.
I better approach for your use-case, would be if you consider this alternative database structure, where each item is the key in a map and all values are true:
orderList: {
"itemMore": true,
"itemMoreValue": true,
"itemMoreOtherValue": true
}
Bafta! ;)

Giving the UID for the document's name

I would like to know what is the best practice to save users preferences in my firestore database.
I would try to explain with an example...
Case 1
I have this kind of Document in my "users" Collection (the name is random generated by Firebase) with 3 fields :
user_uid : String
nickname : String
android_lover : boolean
In my Android project, when I want to search the Document of the user "DFDDE45554SDC", I search where user_uid = "DFDDE45554SDC".
Case 2
I have this kind of Document in my "users" Collection (the name is created with the UID of the user) with 2 fields :
nickname : String
android_lover : boolean
In my Android project, when I want to search the Document of the user "DFDDE45554SDC", I just search the Document "DFDDE45554SDC".
I specify : I don't want duplicate users.
So, what is the best practice (security, optimisation,...) ? Why ?
I would suggest that Case 2 is more effective, for a few reasons:
We already know the user's ID, so don't need to use a different ID here.
Using usersCollection.document(userId) is simple to construct and is a direct DocumentReference, rather than a Query, therefore:
A DocumentReference can be stored in the Firestore database, whereas a Query cannot.
A DocumentReference would likely scale better than instructing the Firestore database to perform a filter query using whereEqualTo("user_uid", userId) (although with indexing, the performance difference is likely negligible at this point).
A Query will always return a collection of results (even if there is only 1), rather than the exact document.
There isn't currently a need for a different randomly-generated ID for each document within the users collection because the user ID is already unique.
You only need 1 document for each user, so this is a sure-fire way to ensure there won't be any duplicates.
The only real incentive I can think of to use Case 1 would be to standardise your document naming scheme with other collections in your database, but this doesn't really matter so much with Firestore.
For a quick example of the two in Android:
Case 1
db.collection("users")
.whereEqualTo("user_uid", "DFDDE45554SDC")
.limit(1)
.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (DocumentSnapshot document : task.getResult()) {
// Even with limit(1), we still receive a collection
// so iterate this to obtain the desired document
}
}
}
});
Case 2
db.collection("users")
.document("DFDDE45554SDC")
.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful() && task.getResult() != null) {
// We have access to the single desired document directly
DocumentSnapshot document = task.getResult();
}
}
});

Firebase database - Query by list of ids

I have recently started switching my app from Parse to Firebase. Everything is going great so far, but I have not been able to find a Firebase equivalent method to Parse's whereContainedIn(String key, Collection values).
This was a very useful method that allowed me to pass in an array of ids and it would return all of the rows that matched that id. So far with Firebase, I have it returning all all of the rows in the database then looping through them to see if that row's id is contained in my array of ids. The other way is to query for each id individually, which doesn't work well with Firebase's asynchronous behavior. This is what I am doing now for the first approach:
List<String> userIds = new ArrayList<>();
userIds.add("2");
userIds.add("32");
userIds.add("12545");
userIds.add("187");
DatabaseReference firebaseRef = FirebaseDatabase.getInstance().getReference();
Query queryRef = firebaseRef.child("videos");
queryRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
List<Video> usersVideos = new ArrayList<Video>();
for (DataSnapshot videoSnapshot : dataSnapshot.getChildren()) {
Video video = videoSnapshot.getValue(Video.class);
if (userIds.contains(video.userId)) {
usersVideos.add(video);
}
}
// usersVideos is now populated with the videos for the users
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.d(TAG, "CANCELLED");
}
});
Neither of these methods seem reasonable since my table will contain hundreds of thousands of records.
Any help would be greatly appreciated!
Make another reference that can be looked up by userId and have it return all videoIds that this user has. From there you can query the videos. That means each time you save you will need to save in two spots. Sort of a pain, but it is what Firebase recommends.
So your reference will look more like:
videos -
| - <videoId> -
| - <video stuffs>
| - userId
videosByUser -
| - <userId> -
| 0 - <videoIdA>
| 1 - <videoIdB>
| - <userId2> -
| 0 - <videoIdA>
can't you just perhaps do a for loop over your ids and inside the for loop write a valueventlistener that will be called for each iterated item`?

Categories

Resources