0

I need to retrieve a users name from my Firebase database from their UID. This is my code for doing this, but I cannot work out how to get this working. The thread goes from:

final String[] returnName = new String[1];

Immediately to:

return returnName[0].toString();

Without waiting for it to populate data from the listener first, so the return value is null and the app crashes. Here is my full code for this module:

private synchronized String getFriendName(String key) {
    final String[] returnName = new String[1];
    DatabaseReference ref = FirebaseDatabase.getInstance().getReference().child("Profiles").child(key).child("Name");
    ref.addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            for (DataSnapshot datas : dataSnapshot.getChildren()) {
                String userResult = datas.getKey();
                if (userResult != null) {
                    returnName[0] = dataSnapshot.getValue().toString();
                    String temp = dataSnapshot.getValue().toString();
                    Toast.makeText(getBaseContext(), "Step 1: " + temp, Toast.LENGTH_SHORT).show();
                    Toast.makeText(getBaseContext(), "Step 2: " + returnName[0], Toast.LENGTH_SHORT).show();
                }
            }
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {

        }
    });
    return returnName[0].toString();
}

I have spent hours reading and trying stuff to get this to work but I cannot make the code actually execute properly first before firing off the return value.

Can anyone help me?

@TaherKorshidi This is the code that calls this function:

GeoQuery geoQuery = geoFire.queryAtLocation(new GeoLocation(currentLocation.latitude, currentLocation.longitude), radius);
geoQuery.removeAllListeners();

geoQuery.addGeoQueryEventListener(new GeoQueryEventListener() {
    @Override
    public void onKeyEntered(String key, GeoLocation location) {
        friendID = key;
        String friend = getFriendName(friendID);
    }
}

Answer, @TaherKorshidi the answer was to get the other values in the same listener. There is no other way around this and I wasn't sure how to do it until you pointed me in this direction. Working solution:

geoQuery.addGeoQueryEventListener(new GeoQueryEventListener() {
                @Override
                public void onKeyEntered(String key, GeoLocation location) {
                    getFriendKey = key;
                    if (getFriendKey != ping_userID) {
                        for(ContactsList d : UserList){
                            if(d.getUID() != null && d.getUID().contains(getFriendKey)) {
                                friendName = d.getName();
                            }
                        }
                        Toast.makeText(getBaseContext(), "Name: " + String.valueOf(friendName), Toast.LENGTH_SHORT).show();
                        getFriendLocation(getFriendKey, friendName);
                    }
                }
Bisclavret
  • 1,327
  • 9
  • 37
  • 65
  • please share cod that call `getFriendName` – Farvardin Feb 24 '18 at 07:19
  • Does this really matter? Like, can that affect the running of this module? That doesn't make sense to me at all. I will edit the question and add this section for you. – Bisclavret Feb 24 '18 at 07:22
  • 1
    yes. it seems that you access `Firebase` using listener. so you need to call `getFriendName` using `listener` too. – Farvardin Feb 24 '18 at 07:25
  • I don't understand mate, how would that look like in code? – Bisclavret Feb 24 '18 at 07:30
  • Are you doing this in Android? – Reaz Murshed Feb 24 '18 at 07:30
  • @Reaz Yeah mate, Android Studio. – Bisclavret Feb 24 '18 at 07:30
  • 3
    Possible duplicate of [How to return dataSnapshot value as a result of a method?](https://stackoverflow.com/questions/47847694/how-to-return-datasnapshot-value-as-a-result-of-a-method) – Alex Mamo Feb 24 '18 at 07:35
  • @Alex Mamo, This post sort of concludes to me that it can't be done. I tried to implement that accepted method but it makes my app function even less than it already did. I think I have to give up. I am instead going to write a method that populates all the data I want to search later into an array at user login. Then I can just access it as I require and more importantly, WHEN I require. There is no other way. Thank you everyone for your input. – Bisclavret Feb 24 '18 at 08:06
  • I can't call the data inside the onDataChange() method, because the data I need requires another onDataChange() function inside the first. I used the custom callback method exactly word for word as it is described in the answer, and it never gets called. I call it specifically in my onDataChange() method, and it does nothing, stepping through debugger just ignores it completely. The queryeventlisetener stops working too. It doesn't crash but it used to sort of work, and now it finds nothing. That answer is a disaster, and I must get out of this rabbit hole and go to arrays. – Bisclavret Feb 24 '18 at 12:05

2 Answers2

1

You can use asyncTask for this purpose as below

public static class GetFriendName extends AsyncTask<String, Void, Void>{

    String returnName;

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        //Show progress bar
    }

    @Override
    protected Void doInBackground(String... strings) {
        DatabaseReference ref = FirebaseDatabase.getInstance().getReference().child("Profiles").child(key).child("Name");
        ref.addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                for (DataSnapshot datas : dataSnapshot.getChildren()) {
                    String userResult = datas.getKey();
                    if (userResult != null) {
                        returnName = dataSnapshot.getValue().toString();
                        String temp = dataSnapshot.getValue().toString();
                    }
                }
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {

            }
        });
        return null;
    }

    @Override
    protected void onPostExecute(Void aVoid) {
        super.onPostExecute(aVoid);
        //Dismiss progress bar
        showFriendName(returnName);
    }
}

you can call this class from onCreate() as below

GeoQuery geoQuery = geoFire.queryAtLocation(new GeoLocation(currentLocation.latitude, currentLocation.longitude), radius);
geoQuery.removeAllListeners();

geoQuery.addGeoQueryEventListener(new GeoQueryEventListener() {
    @Override
    public void onKeyEntered(String key, GeoLocation location) {
        friendID = key;
        new GetFriendName().execute(friendID);
    }
}

then you can assign your friendName to a string value by using this method and do your work there

private static void showFriendName(String friendName){
    Toast.makeText(stackOverflowActivity, ""+friendName, Toast.LENGTH_SHORT).show();
    String friend = friendName;
    //Do your work here
}

this method is calling from onPostExecute().

Sagar A
  • 89
  • 7
0

one way is to use CountDownLatch class:

private synchronized String getFriendName(String key) {
    final String[] returnName = new String[1];
    DatabaseReference ref = FirebaseDatabase.getInstance().getReference().child("Profiles").child(key).child("Name");
    final CountDownLatch latch = new CountDownLatch(1);
    ref.addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            for (DataSnapshot datas : dataSnapshot.getChildren()) {
                String userResult = datas.getKey();
                if (userResult != null) {
                    returnName[0] = dataSnapshot.getValue().toString();
                    String temp = dataSnapshot.getValue().toString();
                    Toast.makeText(getBaseContext(), "Step 1: " + temp, Toast.LENGTH_SHORT).show();
                    Toast.makeText(getBaseContext(), "Step 2: " + returnName[0], Toast.LENGTH_SHORT).show();
                    latch.countDown();
                }
            }
            latch.countDown();
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {

        }
    });
    try {
        latch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return returnName[0].toString();
}

although it solve your problem, but it is not a good solution.

Farvardin
  • 5,336
  • 5
  • 33
  • 54
  • If this works, I am open to looking at it. Why is it not a "good" solution? Does it not scale? Is it prone to crashing the application? Thank you. – Bisclavret Feb 24 '18 at 13:36