19

I have a collection in firestore where each document contains an array of contacts and I want to query those documents where any contact's email id is a certain value.

I came across whereArrayContains() filter at https://firebase.google.com/docs/reference/android/com/google/firebase/firestore/Query#whereArrayContains(java.lang.String,%20java.lang.Object), with the following description:-

public Query whereArrayContains (String field, Object value)
Creates and returns a new Query with the additional filter that documents must contain the specified field, the value must be an array, and that the array must contain the provided value.

A Query can have only one whereArrayContains() filter.

Can the value in the above method point to a field inside an object for an array of objects?

Also, the phrase the value must be an array is a little confusing given the method parameter is also called value. I am sure the documentation means that the field should be present in the document and its value should be an array and that the array should contain the value parameter.

Nikhil Agarwal
  • 529
  • 1
  • 4
  • 16
  • What do you mean through `an object for an array of objects`? Please also post your database structure. – Alex Mamo Sep 16 '18 at 09:13
  • In each document I have a group of contacts. One example of a document would be {groupName:'group1', contacts: [{name: 'Abc', email:'abc@abc.com'}, {name:'Def', email:'def@abc.com'}]}. Now my aim is to fetch all groups where contacts array contains the email 'abc@abc.com'. Is it possible to do this using array-contains operator? – Nikhil Agarwal Sep 16 '18 at 16:07
  • It might work but please add a screenshot of your database structure to see it more clearly. – Alex Mamo Sep 17 '18 at 09:10

4 Answers4

32

You cannot query fields of objects in arrays in a Firestore query. The array-contains query will instead compare with the objects in an array.

the value must be an array

This refers to the value of the field you are trying to query against. Better phrasing for this would be

Creates and returns a new Query with the additional filter that documents must contain the specified field, the value of the specified field must be an array, and that the array must contain the provided value.

If you are trying to filter for a user ID or something similar, consider adding a second array to the object you are querying, then adding the IDs as strings to that array:

{
    "name": "test",
    "users": [someUserObject, someOtherUserObject],
    "userIds": ["someId", "someOtherId"]
}

then query that array instead:

someRef.whereArrayContains("userIds", someId);
Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
Carlo Field
  • 644
  • 4
  • 10
  • 4
    Also see https://stackoverflow.com/q/54081799, which has an answer showing how to do an `array-contains` with objects (which requires the entire object to be specified). – Frank van Puffelen Apr 07 '19 at 03:02
1

According to the documentation, the field point to the array and value means the String you want to check is present in array or not.

You can use the array_contains operator to filter based on array values. For example:

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.

The data is added as:-

CollectionReference cities = db.collection("cities");

Map<String, Object> data1 = new HashMap<>();
data1.put("name", "San Francisco");
data1.put("state", "CA");
data1.put("country", "USA");
data1.put("capital", false);
data1.put("population", 860000);
data1.put("regions", Arrays.asList("west_coast", "norcal"));
cities.document("SF").set(data1);
Raj
  • 2,997
  • 2
  • 12
  • 30
0

As others have said, it's not possible to do what you ask.

The "Firebase-y" way to do what you want, is to create a sub-collection rather than an array, and then each object in the array is instead a document in the sub collection.

You can then query the individual field in the sub-collection using collection-group queries.

If you need access to the parent document, you'll can access it by calling

snapshot.document.parent.parent

which returns a DocumentReference. (The parent of the document is a collection, so its .parent.parent to get the parent document). You'll then have to re-query for that reference.

Iskeraet
  • 731
  • 1
  • 6
  • 12
0

Instead of using two arrays like the accepted answer, you can use map instead, structure your data like this:

{
    users: {
        userId1: {
            name: "...",
            //rest of the fields
        },
        userId2: {
            name: "...",
            //rest of the fields
        }
    }
    //rest of the fields
}

Then use this query to filter by an id

collectionRef.orderBy("users.userId1")