Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to synchronously load data from Firebase?

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?

like image 671
Dani Avatar asked Jul 29 '15 12:07

Dani


People also ask

Is Firebase synchronous?

There is no way to synchronously load data from the Firebase Database.

How does Firebase synchronization work?

Key capabilities Instead of typical HTTP requests, the Firebase Realtime Database uses data synchronization—every time data changes, any connected device receives that update within milliseconds. Provide collaborative and immersive experiences without thinking about networking code.

Is firestore asynchronous?

Since when reading data from firestore, it happens asynchronously.

Can we retrieve data from Firebase?

Firebase data is retrieved by either a one time call to GetValueAsync() or attaching to an event on a FirebaseDatabase reference. The event listener is called once for the initial state of the data and again anytime the data changes.


4 Answers

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

Tasks.await(taskFromFirebase);
like image 198
Alex Avatar answered Oct 12 '22 23:10

Alex


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.

like image 20
Frank van Puffelen Avatar answered Oct 12 '22 23:10

Frank van Puffelen


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!

like image 42
juls Avatar answered Oct 12 '22 23:10

juls


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.

like image 44
mrts Avatar answered Oct 12 '22 22:10

mrts