kevinthakar
kevinthakar

Reputation: 41

Wait for Firebase Async Callbacks to finish inside a loop

I'm trying to fetch a bunch of data that is scattered in different paths in my Firebase Realtime Database. I run a for loop n times, and in each of those iterations, the path to the database is determined based on some logic.

Then I create a Firebase Query (Limits the firebase child response to 1 for each path in each iteration) and attach an addListenerForSingleValueEvent to it. Inside onDataChange, I append the data element received to a fixed-size List (X) using a variable(index) declared final inside the for a loop. That way, the index variable keeps track of all n calls to the database as if they were running in parallel and I can map this List X to a couple of other Lists. So far so good.

Now once these n async callbacks are finished returning their response, I want to call a custom interface callback to send this data to some part of the program.

But obviously, the for loop doesn't wait for these n network operations to get over, and my List X stays empty at the instance I try to call my custom callback interface.

How do I call my custom interface callback AFTER the n database calls are done with their job?

Structure of DB

Code is like this:

.
.
.

for (int i = 0; i < n; i++) {
            final int index = i;

.
.
.
            //Y and X = some variable that changes every loop
            //countryCode = some variable that changes every loop

            Query query = mReference.child(Y + "/" + countryCode + "/" + X).limitToFirst(1);
            query.addListenerForSingleValueEvent(new ValueEventListener() {

                @Override
                public void onDataChange(@NonNull DataSnapshot snapshot) {
                    for (DataSnapshot D : snapshot.getChildren()) {
                        <DataType> n = D.getValue(DataType.class);
                        keys.add(D.getKey());
                        X.set(index, n);
                    }
                }

                @Override
                public void onCancelled(@NonNull DatabaseError error) {
                }
            });
        } //end of for loop

       //Control reaches here before callbacks are over
       //Want to call custom callback here once all data is received

Upvotes: 3

Views: 680

Answers (1)

Ivar
Ivar

Reputation: 4891

One way to achieve what you want is to use a Future. You could wait until all responses return by wrapping those in one big CompletableFuture<Boolean>. A rough example on top of what you wrote above:

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

...

private CompletableFuture<Boolean> queryDatabaseAsync() {

        CompletableFuture<Boolean> future = new CompletableFuture<>();

        // if at least one fails then flag will switch
        AtomicBoolean isSuccess = new AtomicBoolean(true);
        AtomicInteger stack = new AtomicInteger(n);

        for (int i = 0; i < n; i++) {
            final int index = i;
            Query query = ...;
            query.addListenerForSingleValueEvent(new ValueEventListener() {
                @Override
                public void onDataChange(@NonNull DataSnapshot snapshot) {
                    ...
                    // finish if last
                    if (stack.decrementAndGet() < 1) {
                        future.complete(isSuccess.get());
                    }
                }

                @Override
                public void onCancelled(@NonNull DatabaseError error) {
                    ...
                    isSuccess.set(false);
                    // finish if last
                    if (stack.decrementAndGet() < 1) {
                        future.complete(isSuccess.get());
                    }
                }
            });
            
        }

        return future;
}

private void hitDatabase() {

    if (myFuture != null) {
        myFuture.cancel(true);
        myFuture = null;
    }

    myFuture = queryDatabaseAsync();
    myFuture.thenAccept(isSuccess -> {
        // yay!
    });
}

Upvotes: 4

Related Questions