2

I'm quite new to Firebase Realtime database. I'm working on an android app which requires data to be retrieved from Firebase Realtime DB. I've been able to retrieve data using the Firebase listener methods in most occasions but on one occasion, I've found it unable to get a value in the way that I want (I think because of the asynchronous nature).

SCENARIO:-

I have a data structure in Firebase Realtime Database as follows:

For rating:

database1

For users:

database2

I have a separate java class (Suggest.java) that has one simple method to get a list of users (not shown here since it is the basic implementation) and the method defined below to retrieve data from the above ratings node to a list:

public List<String> getMyUnratedPosts(final String currentUserId){
    final List<String> currentUserUnratedPosts = new ArrayList<>();
    DBRCurrentUnratedList = FirebaseDatabase.getInstance().getReference().child("adRat").child(currentUserId);
    DBRCurrentUnratedList.addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
                AdRat adr = snapshot.getValue(AdRat.class);
                int vr = adr.getViewRating();
                if(vr < 1) {
                    currentUserUnratedPosts.add(adr.getAdId());
                }
            }
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {

        }

    });
    return currentUserUnratedPosts;
}

The AdRat class defined above is just a class with attributes (and getters and setters) which matches the database node given above.

I'm creating an instance of this class (Suggest.java) in the onCreate() method of my activity. Then first I'm calling a method in which I'm simply getting a list of users (very similar to above method in which I get the ratings). Then the next line of code in my activity I use a for loop to get each user from the list I got and use that userId for the method shown above. For example, it would be as follows:

Suggest sj = new Suggest();
List<String> users = sj.getUsers();
for (final String mUser: users) {                 
    //Just trying to use the user from above here
    List<String> myUnratedItems = sj.getMyUnratedPosts(mUser);
    Log.d(TAG, "list contains : " + myUnratedItems.toString());
}

PROBLEM :-

I do not get the intended result from the code when I run this. When I debug it, I will get null for the list of users. I think since the firebase methods are asynchronous they do not give the result exactly at the next line of code (that is the place where I need it) but gives the result a bit later (but then the program has run over the line of code in which I need that data).

MY OVERALL NECESSITY : -

Get the list of users to use it in the next line of code (for the next method).

I've gone through several questions in Stackoverflow in regard to this, but couldn't find an exact answer for this.

Please kindly explain as to why this happens and any workaround for this....

P.S. : - I'm new to stackoverflow, so please bear with me any mistakes done in composing the question. Thanks.

PradyumanDixit
  • 2,372
  • 2
  • 12
  • 20
  • Firebase requests are asynchronous, so your return currentUserUnratedPosts; will be alwayys 0 or null because you are trying to return something that has not been loaded yet. – Gastón Saillén Nov 14 '18 at 05:32
  • Please check the duplicate to see why do you have this behaviour and how can you solve this using a custom callback. – Alex Mamo Nov 14 '18 at 12:11
  • Hey @VandyWhite do mark the answer as correct by clicking the V type tick mark button next to the answer, and vote it up by clicking the up arrow button next to the answer, this helps people with similar question on Stack Overflow and I'd also appreciate that. Cheers! – PradyumanDixit Dec 26 '18 at 04:18

2 Answers2

0

Indeed, the eventListeners and calls to Firebase have asynchronous behaviour and thus you have correctly understood your problem.

Unfortunately the solution I have(which almost certainly works) is that you may have to change your approach because of this.

So you should go for an event driven approach. Hence, instead of "wait for the data to come and and then do my thing", you should "when the data comes in, do my thing".

Meaning according to your problem, firs you should retrieve the list of users from the Firebase as you have correctly done in the onDataChange() of valueEventListener, and beneath it, do your thing which you intend to do.

The problem is that since Firebase has asynchronous calls, your data gets printed before it can retrieved, which obviously would be null if you haven't initialised it to something else. So all further operations that you do, occur on null.

So it would be better for you to execute your method or code inside the valueEventListener itself.

Something like this in your given code:

public List<String> getMyUnratedPosts(final String currentUserId){
    final List<String> currentUserUnratedPosts = new ArrayList<>();
    DBRCurrentUnratedList = FirebaseDatabase.getInstance().getReference().child("adRat").child(currentUserId);
    DBRCurrentUnratedList.addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
                AdRat adr = snapshot.getValue(AdRat.class);
                int vr = adr.getViewRating();
                if(vr < 1) {
                    currentUserUnratedPosts.add(adr.getAdId());
                }
            }

            // do your thing right here
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {

        }

    });
    return currentUserUnratedPosts;
}

Other than that, one more approach I found, when I searched though, is Task.await(). But, I won't be counting on it.

You may read more about that here:

StackOverflow Question

StackOverflow Answer

PradyumanDixit
  • 2,372
  • 2
  • 12
  • 20
0

You're exactly right in that it is caused by the asynchronicity of the Firebase calls. The kind of system you'll most likely want to employ is one in which you "subscribe" to know when calls have completed. You can use a callback function, or some other method, such as adding a field/piece of data to a list, and having another data structure periodically check the list for updates, in order to know when the async call has completed.

Your flow will probably be something like this:

  1. "subscribe" to the asynchronous data structure
  2. tell asynchronous data structure to execute
  3. on completion of the asynchronous call, perform some action.

I'd recommend looking at the following link on Asynchronous callbacks in Java from Geeksforgeeks: https://www.geeksforgeeks.org/asynchronous-synchronous-callbacks-java/

loganrussell48
  • 1,656
  • 3
  • 15
  • 23