0

I am new to Android’s background tasks. I am using Firestore to perform the following tasks:

  1. Read a document. https://firebase.google.com/docs/firestore/query-data/get-data

    DBInstance.collection("restaurants")
                .get()
                .addOnCompleteListener(task -> {
                    if (task.isSuccessful()) {
    
                        for (QueryDocumentSnapshot document : Objects.requireNonNull(task.getResult())) {
    
                         // some other code
    
                        }
                    } else {
                        Log.d(TAG, "Error getting documents: ", task.getException());
                    }
    
    
                });
    
  2. Listen to realtime updates of another document. https://firebase.google.com/docs/firestore/query-data/listen

    final DocumentReference docRef = DBInstance.collection("users").document(FirebaseAuth.getInstance().getCurrentUser().getUid());
        docRef.addSnapshotListener(new EventListener<DocumentSnapshot>() {
            @Override
            public void onEvent(@Nullable DocumentSnapshot snapshot,
                                @Nullable FirebaseFirestoreException e) {
                if (e != null) {
                    Log.w(TAG, "Listen failed.", e);
                    return;
                }
    
                String source = snapshot != null && snapshot.getMetadata().hasPendingWrites()
                        ? "Local" : "Server";
    
                if (snapshot != null && snapshot.exists()) {
                    Log.d(TAG, source + " data: " + snapshot.getData());
    
                    // some other code is run
    
                } else {
                    Log.i(TAG,"no snapshot found");
                }
            }
        });
    

Since these are asynchronous processes, they are performed at the same time (roughly).

I want to trigger an independent method when 1. is completed AND when 2. return a non-null snapshot. Therefore, when some other code comments above have been completed.

So, I essentially want some background process that sits idle/ listens for the above two conditions and perform a task/call a method that updates certain UI features.

I have briefly read about BroadcastReciever. Is this relevant? or maybe can I create a custom listener that runs in a background thread? Any suggestions would be helpful since I am not sure what to search for in order to find what I want.

solutions that seems to work (partly suggested by Nehal)

This is the same code as above with the blanks filled in

DBInstance.collection("restaurants")
            .get()
            .addOnCompleteListener(task -> {
                if (task.isSuccessful()) {

                    for (QueryDocumentSnapshot document : Objects.requireNonNull(task.getResult())) {

                     restaurantsLoaded = true;
                     updateUI();

                    }
                } else {
                    Log.d(TAG, "Error getting documents: ", task.getException());
                    restaurantsLoaded = false;
                }


            });


    final DocumentReference docRef = DBInstance.collection("users").document(FirebaseAuth.getInstance().getCurrentUser().getUid());
docRef.addSnapshotListener(new EventListener<DocumentSnapshot>() {
    @Override
    public void onEvent(@Nullable DocumentSnapshot snapshot,
                        @Nullable FirebaseFirestoreException e) {
        if (e != null) {
            Log.w(TAG, "Listen failed.", e);
            return;
        }

        String source = snapshot != null && snapshot.getMetadata().hasPendingWrites()
                ? "Local" : "Server";

        if (snapshot != null && snapshot.exists()) {
            Log.d(TAG, source + " data: " + snapshot.getData());

            usersSnapshotTriggered = true;
            udpateUI();

        } else {
            Log.i(TAG,"no snapshot found");
        }
    }
});


public void updateUI(){
    if(usersSnapshotTriggered && restaurantsLoaded){
        // perform the updates
    }
}
Community
  • 1
  • 1
bcsta
  • 1,963
  • 3
  • 22
  • 61

2 Answers2

1

You can try below solution: Declare a global int variable, increment that variable in both firebase listener and call someMethod() from both listener.

private int count=0;

DBInstance.collection("restaurants")
            .get()
            .addOnCompleteListener(task -> {
                if (task.isSuccessful()) {

                    for (QueryDocumentSnapshot document : Objects.requireNonNull(task.getResult())) {

                     // some other code

                    }
                    count++;
                    someMethod();
                } else {
                    Log.d(TAG, "Error getting documents: ", task.getException());
                }


            });


final DocumentReference docRef = DBInstance.collection("users").document(FirebaseAuth.getInstance().getCurrentUser().getUid());
    docRef.addSnapshotListener(new EventListener<DocumentSnapshot>() {
        @Override
        public void onEvent(@Nullable DocumentSnapshot snapshot,
                            @Nullable FirebaseFirestoreException e) {
            if (e != null) {
                Log.w(TAG, "Listen failed.", e);
                return;
            }

            String source = snapshot != null && snapshot.getMetadata().hasPendingWrites()
                    ? "Local" : "Server";

            if (snapshot != null && snapshot.exists()) {
                Log.d(TAG, source + " data: " + snapshot.getData());

                // some other code is run
                    count++;
                    someMethod();
                    //Note : this method will call as many times as there is change in this data , so you have to handle according to your requirement
            } else {
                Log.i(TAG,"no snapshot found");
            }
        }
    });


private void someMethod(){
    if(count>=2){
       //execute your code
   }
}

Hope this will help!!

Nehal Godhasara
  • 787
  • 3
  • 7
  • This was going to be my solution (and might still be). Was not sure if this is the right way to do things, and was looking for other solution people suggest. But definitely could be the right answer. – bcsta Jan 23 '20 at 09:36
  • Yes, please try and let me know if it works for you. – Nehal Godhasara Jan 23 '20 at 09:39
  • Unfortunately, this is not the way to solve this kind of problem. That global int variable will always have the default value. Basically, you're trying to use a value synchronously from an API that's asynchronous. That's not a good idea. You should handle the APIs asynchronously as intended. Check **[this](https://stackoverflow.com/questions/48499310/firestore-object-with-inner-object/48500679)** out. – Alex Mamo Jan 23 '20 at 10:22
  • @AlexMamo why would the int variable not change? once task is completed the increment will take effect right? if the int variable be replaced with boolean flags, will that solve the issue? – bcsta Jan 23 '20 at 11:28
  • @user7331538 with boolean variable you can't achieve this, as for this situation we have to consider 3 different states of variable(initial, method1 state,method2 state) – Nehal Godhasara Jan 23 '20 at 11:30
  • @user7331538 Because you cannot simply use the result of an asynchronous operation simply in another asynchronous operation. Every time you need to wait for the data. And that [can be solved only in this way](https://stackoverflow.com/questions/48499310/how-to-return-a-documentsnapshot-as-a-result-of-a-method/48500679#48500679). – Alex Mamo Jan 23 '20 at 11:31
  • @NehalGodhasara You cannot solve this issue either with a boolean value because the operation of adding a listener is asynchronous and returns immediately and the callback from the Task it returns will be called sometime later. – Alex Mamo Jan 23 '20 at 11:33
  • @AlexMamo I already said, we cannot used boolean to achieve this, but my given answer will do the trick for sure. – Nehal Godhasara Jan 23 '20 at 11:37
  • @AlexMamo with two independent boolean values the asynchronous issue should be solved. I updated the question including this solution what do you think? – bcsta Jan 23 '20 at 11:41
  • @NehalGodhasara Are you sure? Have you tried it, or it's just a guess? I'm afraid it's not gonna work. – Alex Mamo Jan 23 '20 at 11:43
  • @user7331538 yes you can do that way also, overall it is same as I suggested. – Nehal Godhasara Jan 23 '20 at 11:44
  • @user7331538 * with two independent boolean values the asynchronous issue should be solved.*, no, it won't. *what do you think?* it won't. – Alex Mamo Jan 23 '20 at 11:44
  • @AlexMamo I tried it out and it worked. So if you can elaborate even more and prove me wrong that would help. – bcsta Jan 23 '20 at 11:45
  • @user7331538 ,Alex yes it will work,it was worked for me, just try it and let us know if it works for you. – Nehal Godhasara Jan 23 '20 at 11:45
  • @NehalGodhasara I'm not gonna argue with you guys but the result of two asynchronous operations will never work in that way. – Alex Mamo Jan 23 '20 at 15:42
1

I want to trigger an independent method when both 1. and 2. have been completed

In your first example, by adding a complete listener, you'll always be able to know when the operation is complete. If the task.isSuccessful() returns true you know for sure that the operation is completed. Besides that, you can also call getResult() to get the elements that are apart of your restaurants collection. Furthermore, the following line of code:

DBInstance.collection("restaurants")
        .get()

Returns a Task<QuerySnapshot> object. If you have had two different queries, you could pass both Task objects to Tasks's whenAllSuccess() method, as explained in my answer from the following post:

In this way, you'll be able to know when both operations are completed. However, when using the second solution, you cannot know when getting the data from the database is completed because Cloud Firestore is a real-time database and getting data might never complete. That's why is named a real-time database because at any moment the database can be changed, items can be added or deleted.

The only way to partially know if you have all the data in a particular collection is to perform a single value type query on it. Even then, the data may change after that listener is invoked, so all you really have is a snapshot at a particular moment in time.

As a conclusion, the only solution that you have is to use whenAllSuccess() and pass two or even more Task objects.

I have briefly read about BroadcastReciever. Is this relevant?

No, it's not. According to the docs, the BroadcastReceiver class is:

Base class for code that receives and handles broadcast intents sent by Context.sendBroadcast(Intent).

So, it's not the case.

or maybe can I create a custom listener that runs in a background thread?

The Cloud Firestore client already runs all network operations in a background thread. This means that all operations take place without blocking your main thread. Putting it in a background thread does not give any additional benefits.

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • `so all you really have is a snapshot at a particular moment in time.` That is ok. What I want is: "If 1. is complete and 2. has a snapshot that exists, run the method". I understand 2. is real-time and will have a snapshot every time an event occurs. That is specifically what I want. I want to trigger a method (which is a UI update) every time there is a real-time snapshot AND 1. is complete. So I think Nehal's answer is still a better, simpler solution. – bcsta Jan 23 '20 at 11:16
  • Nehal's answer will not even work because of the asynchronous nature of these operations. *If 1. is complete and 2. has a snapshot that exists, run the method* then just move the "2" inside "1", right inside the `onComplete()` method. Otherwise, you have to implement the solution I showed you in my answer above. – Alex Mamo Jan 23 '20 at 11:28