Wait until Firebase retrieves data

39.1k views Asked by At

I want to build a method that returns a child value in Firebase. I tried to do something like this:

public String getMessage(){

    root.child("MessagesOnLaunch").child("Message").addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            message = (String) dataSnapshot.getValue();
            Log.i("4r398", "work");
        }

        @Override
        public void onCancelled(FirebaseError firebaseError) {
            Log.e("error", firebaseError.getMessage());
        }
    });
    return message;
}

The problem is that the method returns null. That is probably because the method doesn't wait until the listener finishes and return null because it's the default value of message.

How can I make this method wait until the listener occurs and then return the value?

11

There are 11 answers

0
Ajay Pawar On

Add this code inside onDataChange() method

do{
    message = (String) dataSnapshot.getValue();
    if(message.equals(null)) 
        break;
}while (!message.equals(null));

                 
0
Aayush Mathur On

Firebase usually takes about 1-2 seconds to load the data.

So keeping that in mind you can add a new thread using Handler, to make the system wait for say, 2 Seconds and then use the value retrieved from the Database.

Till then you can display a Loading Dialog box to the user which can be closed once the data is retrieved.

 Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                {
                    loadingDialog.dismiss();//dismiss the dialog box once data is retreived
                    Toast.makeText(Act_NewHome.this, "Not enough players online right now!", Toast.LENGTH_SHORT).show();
tvUserName.setText(u_name);
                }
            }
        }, 2000);// 2000 milliseconds = 2seconds
0
Gnana Sreekar On

You can follow this link https://youtu.be/LVaIArVY52U

Firebase database is quite slow when retrieving the data so to make the application wait for the process to complete you can follow this

Create a new class Ex. Firebasedata.java and extent it with Application and add put this code in the OnCreate sections

public class Firebasedata extends Application {
    @Override
    public void onCreate() {
        FirebaseDatabase.getInstance().setPersistenceEnabled(true);
        super.onCreate();
    }
}

declare the java class in the manifests

 <application
        android:name=".Firebasedata"  <------
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
     ...
     ...
    </application>

Then go back to the class where you want to get the data and create a dialog with this code

    ProgressDialog TempDialog;
    CountDownTimer mCountDownTimer;
    int i=0;

        TempDialog = new ProgressDialog(MainActivity.this);
        TempDialog.setMessage("Please wait...");
        TempDialog.setCancelable(false);
        TempDialog.setProgress(i);
        TempDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
        TempDialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.GRAY));

 TempDialog.show();
        mCountDownTimer = new CountDownTimer(2000, 1000)
        {
            public void onTick(long millisUntilFinished)
            {
                TempDialog.setMessage("Please wait..");
            }

            public void onFinish()
            {
                TempDialog.dismiss();
                //Your action like intents are placed here

            }
        }.start();

You can look into the link above and get a better idea

0
DiLDoST On

After a long research, i couldn't find it so i did it my way. It can be done like this. But this code is not safe on main thread because it waits until the data retrives. So it has much load if you use it on Main Thread . I added the WorkerThread Annotation to tell that it shouldnt be used in UIThread , another new Thread is safe. I tested and it's working on new Thread but not on Main Thread

Class

public static class DBUtils{
    private boolean fetched;
    private DataSnapshot result;
    private DatabaseReference dbr;
    private DatabaseException err;
    
    private DBUtils(DatabaseReference dbr){
        this.dbr=dbr;
    }
    
    private DataSnapshot fetch(boolean error){
        result=null;
        if(dbr!=null){
            fetched=false;
            dbr.addListenerForSingleValueEvent(new ValueEventListener(){

                    @Override
                    public void onDataChange(DataSnapshot data)
                    {
                        result=data;
                        done();
                    }

                    @Override
                    public void onCancelled(DatabaseError e)
                    {
                        err=e.toException();
                        done();
                    }

                    private void done()
                    {
                        fetched=true;
                        //dbr.removeEventListener(this);
                    }
                });
        }else{
            fetched=true;
        }
        while(!fetched){
            // wait
        }
        if(error&&err!=null){
            throw err;
        }
        return result;
    }
    
    @WorkerThread
    public static @Nullable DataSnapshot getRealtimeValue(@NonNull DatabaseReference ref)
    {
        return getRealtimeValue(ref,false);
    }
    
    @WorkerThread
    public static @Nullable DataSnapshot getRealtimeValue(@NonNull DatabaseReference ref, boolean throwError) throws DatabaseException
    {
        return new DBUtils(ref).fetch(throwError);
    }
}

To use

...
DatabaseReference ref = ...: // Your ref goes here
boolean throwError = false; // if true, the code will throw the error occurred during data fetching, else if false, it will just skip it and return null
DataSnapshot result = DBUtils.getRealtimeValue(ref, throwError);
...

Don't forget to upvote me, 'cause i barely need votes to help others more. And also mark this as Solved if this is what you want

— DiLDoST

4
Tirupati Singh On

You can use CountDownLatch. This is how you can use it.

public String getMessage(){
   CountDownLatch done = new CountDownLatch(1);
   final String message[] = {null}; 
   root.child("MessagesOnLaunch").child("Message").addListenerForSingleValueEvent(new ValueEventListener() {

    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        message[0] = (String) dataSnapshot.getValue();
        System.out.println("worked");
        done.countDown();
    }

    @Override
    public void onCancelled(FirebaseError firebaseError) {
        System.out.println("failed"+firebaseError.getMessage());
    }
});

try {
    done.await(); //it will wait till the response is received from firebase.
} catch(InterruptedException e) {
    e.printStackTrace();
}
return message[0];
}
0
Pedro Bacchini On

As @CMikeB1 commented on another response in the java world when we are dealing with servlets and services rest we use sync calls to perform operations, it would be useful to have both options in sdk firebase admin so that the developer could choose which one to use depending on his case use.

I made my implementation from waiting to read the sync data using ApiFuture which is the current interface adopted by the sdk of the firebase admin. here's a link!

public DataSnapshot getMessage() {
    final SettableApiFuture<DataSnapshot> future = SettableApiFuture.create();
    databaseReference.child("MessagesOnLaunch").child("Message").addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            future.set(dataSnapshot);
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {
            future.setException(databaseError.toException());
        }
    });
    try {
        return future.get();
    } catch(InterruptedException | ExecutionException e) {
        e.printStackTrace();
        return null;
    }
}
0
Zod On

I'll leave this here to help anyone who faces the same problem as me. @Tirupati Singh code looks nice but I wrote my answer below his why it didn't work for me. Using Atomic type worked -

public AtomicInteger getMessage(){
        final AtomicBoolean done = new AtomicBoolean(false);
        final AtomicInteger message1 = new AtomicInteger(0);

        //assuming you have already called firebase initialization code for admin sdk, android etc
       DatabaseReference root = FirebaseDatabase.getInstance().getReference("server");
       root.child("MessagesOnLaunch").child("Message").addListenerForSingleValueEvent(new ValueEventListener() {

        public void onDataChange(DataSnapshot dataSnapshot) {
            message1.set((Integer) dataSnapshot.getValue()); 
            done.set(true);
        }

        public void onCancelled(DatabaseError error) {
            // TODO Auto-generated method stub

        }
    });
    while (!done.get());

    return message1;
}

Note the function can only return message type Integer. I couldn't get it to work for String as no Atomic type for String. Hope this helps!

0
austinw On

The topmost answer by @Joseph is almost correct! His answer is definitely the simplest and easiest to understand and implement, so I'm going to piggyback off of his answer and correct one fatal flaw.

Step 1: Like he said above, using a simple callback interface would do the trick, like so:

public interface OnGetDataListener {
    //this is for callbacks
    void onSuccess(String dataSnapshotValue);
}

Step 2: Create a method that accepts an implementation of the interface as a parameter, and queries the Firebase Database. This is where the original answerer's mistake was made... Instead of passing a DataSnapshot to the callback, you must pass the value of the snapshot.

public void readData(Firebase ref, final OnGetDataListener listener) {

    ref.addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {

            // This is how you use the value once it is loaded! Make sure to pass the
            // value of the DataSnapshot object, not the object itself (this was the
            // original answerer's mistake!
            listener.onSuccess(dataSnapshot.getValue());
        }

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

Step 3: Call the above function and pass an implementation of the callback interface that handles the data after it is loaded.

readData(root.child("MessagesOnLaunch").child("Message"), new OnGetDataListener() {
                @Override
                public void onSuccess(String dataSnapshotValue) {

                // This is where you can handle the snapshot's value! (log it, add it 
                // to a list, etc.)

                }
            });
3
Joseph Varghese On

Make an interface

 public interface OnGetDataListener {
    //this is for callbacks
    void onSuccess(DataSnapshot dataSnapshot);
    void onStart();
    void onFailure();
}

Declare the following function readData()

public void readData(Firebase ref, final OnGetDataListener listener) {
    listener.onStart();
    ref.addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            listener.onSuccess(dataSnapshot);
        }

        @Override
        public void onCancelled(FirebaseError firebaseError) {
            listener.onFailure();
        }
    });

}

Call the readData() function as follows

readData(root.child("MessagesOnLaunch").child("Message"), new OnGetDataListener() {
                @Override
                public void onSuccess(DataSnapshot dataSnapshot) {
                    
               //got data from database....now you can use the retrieved data

                                         
                }
                @Override
                public void onStart() {
                    //when starting
                    Log.d("ONSTART", "Started");
                }

                @Override
                public void onFailure() {
                    Log.d("onFailure", "Failed");
                }
            });

readData() can be called inside your getMessage() method or even inside onCreate()

3
Vladyslav Ulianytskyi On

Don't use Firebase as functions that return values—it goes against its asynchronous nature.

Plan a code structure that allows Firebase to perform its task and then within the closure (block) go to the next step.

In your code, for example, change the function to not return anything and within the onDataChange, as the last line call the next function to update your UI.

0
martinseal1987 On

Sorry to be a little late to this party but rather than have firebase return a value you could set up an observer / view model, then when you run your async firebase function the onData method is called from this you would update your view models data which then triggers its onDataChanged method which you can then update your layout from