Is it possible to synchronously load data from Firebase?

16,440

Solution 1

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.

Solution 2

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

Tasks.await(taskFromFirebase);

Solution 3

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!

Solution 4

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.

Share:
16,440
Dani
Author by

Dani

Updated on June 05, 2022

Comments

  • Dani
    Dani about 2 years

    I'm trying to update parts of a WebView in my Android app with data I'm getting from a peer connected via Firebase. For that, it could be helpful to execute blocking operations that will return the needed data. For example, an implementation of the Chat example that will wait until another chat participant writes something before the push.setValue() to return. Is such a behavior possible with Firebase?

  • Dani
    Dani almost 9 years
    Thanks Frank! I agree with the blocking vs events driven approach. However, I have to perform multiple updates to the WebView, the same way a WebView.loadDataWithBaseURL() does and HTTP requests (which the WebView performs) are also blocking. The reason I have to do it instead of the WebView itself is that the server is not approachable to the app and I use Firebase as a "tunnel". Any better solution would be highly appreciated. Also, Isn't the Semaphore solution will fail if the blocking section is inside an event that may be triggered every time the WebView asks for an update?
  • Frank van Puffelen
    Frank van Puffelen almost 9 years
    I'm pretty sure the above snippet addresses the Chat example that you gave. If you use-case is actually different, provide an stackoverflow.com/help/mcve please. Code beats a description of that code every day of the week.
  • bits
    bits about 8 years
    @Dani how is it working? because acquire will block main thread and the release will never be executed because that's also supposed to run on main thread?
  • Bob Snyder
    Bob Snyder about 8 years
    @Frank: Your example using a semaphore may have worked on an earlier version of Firebase, but it doesn't work on 9.0.2. I tried it. The listener callbacks run on the main thread,which is blocked waiting to acquire the semaphore. When I used semaphore.tryAcquire(10, TimeUnit.SECONDS), it always timed-out and then the callback fired.
  • Frank van Puffelen
    Frank van Puffelen about 8 years
    You are right, this doesn't work on Android (anymore). I marked that in a different answer, but not here. Updated and thanks for the ping.
  • Isaak Osipovich Dunayevsky
    Isaak Osipovich Dunayevsky almost 8 years
    problem exists, ios provides good centralized threading mechanisms. With Parse api you could use them by calling synchronous api. Firebase sucks
  • lujop
    lujop almost 8 years
    @Frank It's true that you haven't to block main thread. But sometimes you are already in background threads and synchronous API can make code more readable. But it's true that misused for inexperienced programmers is a problem.
  • Eugen Pechanec
    Eugen Pechanec over 7 years
    Really thanks, I was looking for this too; but how does this solve "execute blocking operations that will return the needed data"? Event listeners are not Tasks, so how do you do that?
  • Eugen Pechanec
    Eugen Pechanec over 7 years
  • Alex
    Alex over 7 years
    this one executes task from firebase in a blocking manner and you either get a response or it throws an error. So you don't need any callbacks
  • inger
    inger about 7 years
    Sounds good, a bit more detail, links and background would be nice before I vote up the answer :)
  • BajaBob
    BajaBob over 6 years
    Firebase team has now deprecated Tasks and is suggesting we use ApiFutures: googleapis.github.io/api-common-java/1.1.0/apidocs/com/googl‌​e/…
  • Johon smuthio
    Johon smuthio over 6 years
    what is this new SnapshotToObjects(); is it any builtin class or you have created your own class. if it is your own class can you kindly tell about it and its parameter
  • juls
    juls over 6 years
    It's implemented by me and is just a mapper for DataSnapshot -> List<T>.
  • juls
    juls over 6 years
    There is now real implementation for this example. In my case it converts the DataSnapshot to a list of recipes. You can find an example here