Is it possible to synchronously load data from Firebase?

On a regular JVM, you'd do this with regular Java synchronization primitives.

For example:

// create a java.util.concurrent.Semaphore with 0 initial permits
final Semaphore semaphore = new Semaphore(0);

// attach a value listener to a Firebase reference
ref.addValueEventListener(new ValueEventListener() {
    // onDataChange will execute when the current value loaded and whenever it changes
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        // TODO: do whatever you need to do with the dataSnapshot

        // tell the caller that we're done
        semaphore.release();
    }

    @Override
    public void onCancelled(FirebaseError firebaseError) {

    }
});

// wait until the onDataChange callback has released the semaphore
semaphore.acquire();

// send our response message
ref.push().setValue("Oh really? Here is what I think of that");

But this won't work on Android. And that's a Good Thing, because it is a bad idea to use this type of blocking approach in anything that affects the user interface. The only reason I had this code lying around is because I needed in a unit test.

In real user-facing code, you should go for an event driven approach. So instead of "wait for the data to come and and then send my message", I would "when the data comes in, send my message":

// attach a value listener to a Firebase reference
ref.addValueEventListener(new ValueEventListener() {
    // onDataChange will execute when the current value loaded and whenever it changes
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        // TODO: do whatever you need to do with the dataSnapshot

        // send our response message
        ref.push().setValue("Oh really? Here is what I think of that!");
    }

    @Override
    public void onCancelled(FirebaseError firebaseError) {
        throw firebaseError.toException();
    }
});

The net result is exactly the same, but this code doesn't required synchronization and doesn't block on Android.


Here's a longer example based on Alex's compact answer:

import com.google.android.gms.tasks.Tasks;
import com.google.firebase.firestore.CollectionReference;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.Query;
import com.google.firebase.firestore.QuerySnapshot;
final FirebaseFirestore firestore = FirebaseFirestore.getInstance(); final CollectionReference chatMessageReference = firestore.collection("chatmessages"); final Query johnMessagesQuery = chatMessageReference.whereEqualTo("name", "john");
final QuerySnapshot querySnapshot = Tasks.await(johnMessagesQuery.get());
final List<DocumentSnapshot> johnMessagesDocs = querySnapshot.getDocuments(); final ChatMessage firstChatMessage = johnMessagesDocs.get(0).toObject(ChatMessage.class);

Note that this is not good practice as it blocks the UI thread, one should use a callback instead in general. But in this particular case this helps.


I came up with another way of fetching data synchronously. Prerequisite is to be not on the UI Thread.

final TaskCompletionSource<List<Objects>> tcs = new TaskCompletionSource<>();

firebaseDatabase.getReference().child("objects").addListenerForSingleValueEvent(new ValueEventListener() {

            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                Mapper<DataSnapshot, List<Object>> mapper = new SnapshotToObjects();
                tcs.setResult(mapper.map(dataSnapshot));
            }

            @Override
            public void onCancelled(DatabaseError databaseError) { 
                tcs.setException(databaseError.toException());
            }

        });

Task<List<Object>> t = tcs.getTask();

try {
    Tasks.await(t);
} catch (ExecutionException | InterruptedException e) {
    t = Tasks.forException(e);
}

if(t.isSuccessful()) {
    List<Object> result = t.getResult();
}

I tested my solution and it is working fine, but please prove me wrong!


import com.google.android.gms.tasks.Tasks;

Tasks.await(taskFromFirebase);