Firebase retrieve data Null outside method

I put the below method inside an onCreate(), when the app launch it will trigger this method. But when I try to to get mName outside the ValueEventListener(), it return null and the app crashed.

Anyone know why this issue happen?

 ValueEventListener postListener = new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            UserDetails info = dataSnapshot.getValue(UserDetails.class);
            Log.d(TAG, String.valueOf(dataSnapshot));

            mName = info.getName();
            **Log.d(TAG, mName); // HERE GET THE NAME**

        }

        @Override
        public void onCancelled(DatabaseError databaseError) {
            Log.w(TAG, "getUser:onCancelled", databaseError.toException());
        }
    };
    mDatabase.child("Admin")
            .child("Info")
            .child(uid)
            .addValueEventListener(postListener);

    **Log.d(TAG, mName); // HERE GET NULL**

Solution 1:

As the others have said: all data from the Firebase Database is read asynchronously. It's easiest to see this by adding some more log statements to your code:

Log.d(TAG, "Before attaching listener");
mDatabase.child("Admin")
        .child("Info")
        .child(uid)
        .addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        Log.d(TAG, "In listener's onDataChange");
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {
        Log.w(TAG, "getUser:onCancelled", databaseError.toException());
    }
});
Log.d(TAG, "After attaching listener");

Unlike what you may expect, the output of this is:

Before attaching listener

After attaching listener

Inside listener's onDataChange

The way to deal with the asynchronous behavior is to reframe your problem.

Right now your code is written: first we load the data, then we log it.

If you reframe the problem to: "whenever the data is loaded, we log it", you get code like this:

mDatabase.child("Admin")
        .child("Info")
        .child(uid)
        .addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        UserDetails info = dataSnapshot.getValue(UserDetails.class);
        Log.d(TAG, String.valueOf(dataSnapshot));

        mName = info.getName();
        Log.d(TAG, mName);

    }

    @Override
    public void onCancelled(DatabaseError databaseError) {
        Log.w(TAG, "getUser:onCancelled", databaseError.toException());
    }
});

So all code that needs the data, needs to be inside the onDataChange(). Alternatively, you can make your own custom callback method and invoke that from onDataChange(). But the logic is always the same: the code that needs toe data, is triggered from onDataChange() when the data is available.

Solution 2:

That's because Firebase still didn't retrieve the data yet at the time you do:

Log.d(TAG, mName);

mName is currently null since this line was executed before Firebase got the data.

To solve this, you have to Log.d(TAG, mName) inside the onDataChange method. Otherwise, it will be null as you haven't set the value yet (since Firebase still didn't get the value yet).