10

I've searched everywhere with no luck. I want to query Firestore to get all users WHERE type is admin. Something like:

SELECT * FROM users WHERE type=admin

but only when the property total is changing. If I'm using:

users.whereEqualTo("type", "admin").addSnapshotListener(new EventListener<QuerySnapshot>() {
    @Override
    public void onEvent(@Nullable QuerySnapshot snapshots, @Nullable FirebaseFirestoreException e) {
        for (DocumentChange dc : snapshots.getDocumentChanges()) {
            switch (dc.getType()) {
                case ADDED:
                    //Not trigger
                    break;
                case MODIFIED:
                    //Trigger
                    break;
                case REMOVED:
                    //
                    break;
            }
        }
    }
});

The case ADDED is triggered first time when I query and when the total is changed case MODIFIED is triggered again (this is what is want). I want only changes and not the all initial data, I don't need it. How to get it?

Please help me, is the last part of my project. How to skip is case ADDED?

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
Sky Tracker
  • 143
  • 1
  • 8

4 Answers4

10

When you are listening for changes in Cloud Firestore for realtime changes, using Firestore Query's addSnapshotListener() method, it:

Starts listening to this query.

Which basically means that first time you attach the listener, you get all documents that correspond to that particular query. Furthermore, everytime a property within a document changes, you are notified according to that change. Obviously, this is happening only if the listener remains active and is not removed.

Unfortunately, Firestore listeners don't work that way, so you cannot skip that "case ADDED". What you can do instead, is to add add under each user object a Date property (this is how you can add it) and query your database on client, according to this new property, for all documents that have changed since a previous time.

According to Nick Cardoso's comment, for future visitors that might ask why this behaviour happens, is because the reason he mentioned in his comment. I also recommend see Doug Stevenson's answer from this post, for a better understanding.

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • Thank for the answer, I try your solution. – Sky Tracker Nov 05 '18 at 17:49
  • Ok, keep me posted. – Alex Mamo Nov 05 '18 at 18:04
  • Hi Sky! Have you tried my solution above, does it work? – Alex Mamo Nov 06 '18 at 07:44
  • This answer is incomplete as it misses a big point (the date solves the issue, but doesn't explain [*why* the behaviour happens](https://stackoverflow.com/a/48491561/984830)). The snapshot provides data from the local cache as well as mutations from the remote. So if you only wan't changes (implying this is a repeated query) it's not fetching (and charging for) everything again. It's getting just the mutations as expected, you just have to handle it appropriately. – Nick Cardoso Nov 09 '18 at 09:25
  • Hi @AlexMamo,thanks, do you have any other solution for this, it is not working in my case : https://stackoverflow.com/questions/54073457/how-to-ignore-the-first-playload-of-addsnapshotlistener-in-firestore-for-android?noredirect=1#comment94979831_54073457 – Raj Jan 07 '19 at 11:43
  • I'm using this approach, but I do not get the REMOVED change type when the listener starts fro the first time. – Cícero Moura May 04 '21 at 00:18
  • @CíceroMoura Without seeing your code, it's hard to say why you have that behavior. So please post a new question using its own [MCVE](https://stackoverflow.com/help/mcve), so I and other Firebase developers can help you. – Alex Mamo May 04 '21 at 09:02
  • @AlexMamo, here is my question: https://stackoverflow.com/questions/67427355/firestore-does-not-notify-remove-change-type-when-querying-documents – Cícero Moura May 07 '21 at 00:01
5

There is an option to check if the querySnapshot is from a cache, changes return false

if(querySnapshot.getMetadata().isFromCache()) return
Artur Gniewowski
  • 470
  • 7
  • 17
  • Thank you! This should be the accepted solution! – Markymark Dec 24 '20 at 23:59
  • Although this didn't really work for me, I thought I would mention that this is now done in the following way `snapshot.metadata.fromCache` as the API has changed. – T Mack Feb 11 '21 at 22:28
  • Thanks a lot! Short and very useful solution! This helps to not mess up the code with booleans fields, showing if it is the first run. – Ghelle Dec 17 '21 at 18:25
3

Here is a solution working for me: use

AtomicBoolean isFirstListener = new AtomicBoolean(true);

and then on event method

if (isFirstListener.get()) {
                    isFirstListener.set(false);
                    //TODO Handle the entire list. 
                    return;
                }

Here is a sample code from my project:

 final AtomicBoolean isFirstListener = new AtomicBoolean(true);
 mDb.collection("conversation_log").document(room_id).collection("messages").orderBy("sent_at")
    .addSnapshotListener(new EventListener<QuerySnapshot>() {
        @Override
        public void onEvent(@Nullable QuerySnapshot value2, @Nullable FirebaseFirestoreException e) {
        if (isFirstListener.get()) {
            isFirstListener.set(false);
            //TODO Handle the entire list.
            return;
        }
    }
});

reference: answer

Raj
  • 946
  • 12
  • 16
  • The AtomicBoolean you use is as good as a default volatile boolean value, because you are not using any special atomic methods of the class, so this code does not prevent race conditions during any parallel executions! Refer: https://stackoverflow.com/a/39352047/6086782 – varun Oct 22 '20 at 07:18
  • 1
    Note: This solution is very very superficial, all it is doing is ignoring the first fetch of data using an (if...else) statement. The only solution to the OP's question is to wait for the firestore team to give us an API which can make the initial snapshot optional. – varun Oct 22 '20 at 07:18
0

Found a work around for this given use case, it is possible to skip the initial data and get only updates. The workaround is including a server timestamp in your structure and build your query to fetch only the data that has timestamp greater than the current time.

val ref = db.collection("Messages").document("Client_ID")
        .collection("Private")
        .orderBy("timestamp")
        .whereGreaterThan("timestamp",Calendar.getInstance().time)

//Add snapshot listener
ref.addSnapshotListener { snapshot , e ->
            if (snapshot != null) {
                Log.d("TAG", "Current data: ${snapshot.documents.size}")
                for(document in snapshot.documents){
                    Log.e("Document Data",document.data.toString())
                }
            }
        }

So, the query won't return any data in the initial build, but will listen to the document changes. As soon as timestamp of a document changes, you'll be notified about that change. Then you can check if the data exists in your list (if you're looking for modifications, or if it's a new document added)

Just update timestamp of the document when your write any changes. As shown below :

val message = hashMapOf<String,Any>(
        "timestamp" to FieldValue.serverTimestamp(),
        "id" to userId,
        "data" to data,
    )
db.collection("Messages").document("Client_ID")
        .collection("Private")
        .document().set(message)