2

I am trying to return data in a method that I get from a listener for single event call. However, it looks like the object I'm returning is being populated after the actual return statement. I understand that the call to get the data snapshot is asynchronous and that is why this is happening. How can I avoid this? I've tried Semaphores and Atomic Booleans but it just seems to lock up my application. Here is the code in question.

static User getUser(String uid){
    /**** created final object here for returning ****/
    final User returnUser = new User();

    Firebase userRef = new Firebase("<firebase-url>/users/"+uid+"/");

    userRef.addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            Log.d("MT", "attempting to instantiate user");
            User tempUser = dataSnapshot.getValue(User.class);

            /**** preparing object for return ****/
            returnUser.setNickname(tempUser.getNickname());
            returnUser.setAge(tempUser.getAge());
            returnUser.setEmail(tempUser.getEmail());
            returnUser.setHeight(tempUser.getHeight());
            returnUser.setSex(tempUser.getSex());
            returnUser.setWeight(tempUser.getWeight());

            //This logs actual information
            Log.d("MT", returnUser.getNickname() + " =======INSTANTIATED=======."); 
            Log.d("MT", returnUser.getEmail());
            Log.d("MT", new Double(returnUser.getAge()).toString());
            Log.d("MT", new Double(returnUser.getHeight()).toString());
            Log.d("MT", returnUser.getSex());
            Log.d("MT", new Double(returnUser.getWeight()).toString());
        }

        @Override
        public void onCancelled(FirebaseError firebaseError) {
            Log.d("MT", "Something went wrong.");
        }
    });
    Log.d("MT", returnUser.getNickname());    //This logs an empty string.
    return returnUser;
}

Note: I've tried Atomic boolean set to false then set to true within the listener and then have a while(boolean == false) before I return but this results in a lockup of my application.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807

1 Answers1

4

What you're attempting to do is make Firebase synchronous.

This may be possible, but it's a very bad idea. It'd risk doing network operations on the main thread. A pattern that makes Android very grumpy. See their general guidance on doing network stuff on a separate thread.

So, having established that it's good to do network stuff on a different thread, I have good news for you. Firebase already does this automatically! Specify a callback, just like you would in an AsyncTask.

Change your getter to accept a callback as a parameter.

public void getUser(String uid, AwesomeCallback callback){

    Firebase userRef = new Firebase("<firebase-url>/users/"+uid+"/");

    userRef.addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            Log.d("MT", "attempting to instantiate user");
            User returnUser = new User();
            User tempUser = dataSnapshot.getValue(User.class);

            /**** preparing object for return ****/
            returnUser.setNickname(tempUser.getNickname());
            returnUser.setAge(tempUser.getAge());
            returnUser.setEmail(tempUser.getEmail());
            returnUser.setHeight(tempUser.getHeight());
            returnUser.setSex(tempUser.getSex());
            returnUser.setWeight(tempUser.getWeight());

            //This logs actual information
            Log.d("MT", returnUser.getNickname() + " =======INSTANTIATED=======."); 
            Log.d("MT", returnUser.getEmail());
            Log.d("MT", new Double(returnUser.getAge()).toString());
            Log.d("MT", new Double(returnUser.getHeight()).toString());
            Log.d("MT", returnUser.getSex());
            Log.d("MT", new Double(returnUser.getWeight()).toString());

            callback.doCallback(returnUser);
        }

        @Override
        public void onCancelled(FirebaseError firebaseError) {
            Log.d("MT", "Something went wrong.");
        }
    });
}

And then, do your work from the callback function

public class SomeAwesomeActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        someObject.getUser("uid123", new AwesomeCallback() {
            @Override
            public void doCallback(User user) {
                // Do cool stuff with that user object here
            }
        });
    }
}

If you haven't done a lot of callback programming in the past, it's a bit weird, and it will take some time to get used to it. However, once you've mastered it, Android development goes much more smoothly. Lots of constructs built into the Android SDK depend on this pattern.

mimming
  • 13,974
  • 3
  • 45
  • 74
  • sorry that this is a super old question, but very useful. How would you define the Callback class `AwesomeCallback`? – beckah Jan 18 '17 at 16:26
  • I usually define an inner class, but it was an arbitrary choice. You just need a class that defines the function that you call from the other method. – mimming Jan 25 '17 at 21:57