1

I am adding data to a map by using a loop which has a callback inside it. I am then passing this map to a Recycler View, but when I do this - the map is empty so nothing is shown. I assume it is because I need to wait for the callbacks to occur?

The loop below is within a class that's called from OnActivityCreated inside my fragment. Here's how my callbacks occur:

    for (final Map.Entry<String, UserDetails> entry : listOfUsers.entrySet()) {
        getSeasonsData("Seasons", entry.getKey(), new CallBack3() {

            @Override
            public void onSuccess(List<UserSeasons> seasons) {
                userSeasons.put(entry.getKey(), seasons);
            }

            @Override
            public void onError(String error) {

            }

        });
    }

How can I wait for all the callbacks to complete within this loop before progressing?

Here is my getSeasonsData method:

 private void getSeasonsData(String pathString, String childNode, final CallBack3 callBack) {
    fbDB.child(pathString).child(childNode).addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {

            List<UserSeasons> usersSeasonsListTemp = new ArrayList<>();

            if (dataSnapshot.exists()) {

                for (DataSnapshot ds : dataSnapshot.getChildren()) {
                    UserSeasons C = ds.getValue(UserSeasons.class);
                    usersSeasonsListTemp.add(C);
                }
                callBack.onSuccess(usersSeasonsListTemp);

            }

        }

        @Override
        public void onCancelled(DatabaseError databaseError) {
            callBack.onError(databaseError.getMessage());
        }
    });
}
PSLDev
  • 77
  • 6
  • 1
    You should overload `getSeasonsData` to take a list of entries, then return one callback – OneCricketeer Oct 25 '17 at 10:05
  • try passing `map` to `RecyclerView` in your `onSuccess` method. – HassanUsman Oct 25 '17 at 10:06
  • @cricket_007 I've added my getSeasonsData method to the question. Could you please explain how I can do that with regards to my case here? – PSLDev Oct 25 '17 at 10:15
  • Well, the fact that you are using Firebase and have `addValueEventListener` within a nested loop. I know that is an anti-pattern, but I cannot answer how to fix it – OneCricketeer Oct 25 '17 at 10:16
  • In other words, the `onDataChange` is intended for realtime access of always changing data. You set the listener, and it always updates data, and that callback is ran. By sticking that in a loop, you are continuously overriding the listener. While the single query pattern is supported for lookups, this pattern indicates you need to restructure your database – OneCricketeer Oct 25 '17 at 10:21
  • That would take some time. But in terms of a quick fix, will the solution of using a counter work, as in your answer below? Would it be possible to adapt that having seen the getSeasonsData method now? – PSLDev Oct 25 '17 at 10:25
  • Possible duplicate of [Waiting for multiple callbacks in Android](https://stackoverflow.com/questions/17418194/waiting-for-multiple-callbacks-in-android) – Bertram Gilfoyle Feb 16 '19 at 19:17

2 Answers2

0

Since you are using Firebase, I think you need to restucture your query or database.

For example, start from fbDB.child(pathString), then you can get childNode and all other children at that path from the snapshot that is returned. With the information from listOfUsers, you should be able to filter the snapshot children, or accumulate a larger dataset and return the full data you need rather than executing multiple queries at several paths.

Original Answer

In theory, you could have some int counter = 0; and final int userSize = listOfUsers.size(), then place an if statement somewhere in the callback for when (i == userSize - 1) to indicate that all events have been processed.

However, I suggest your function look like this. Internalizing the loop.

    getSeasonsData("Seasons", listOfUsers, new Callback() {

        @Override
        public void onSuccess(Map<String, List<UserSeasons>> seasonMap) {
            // Loop over seasonMap  
            // seasons = seasonMap.get(entry.getKey());
            // userSeasons.put(entry.getKey(), seasons);
        }

        @Override
        public void onError(String error) {

        }

    });
}

Within public void getSeasonsData(String arg1, List arg2, Callback arg3), you can loop over the list, performing blocking network calls to add to some Map<String, List<UserSeasons>>, from which you return via the callback.


You could also have the entire Firebase callback rather than relying on your own.

private void getSeasonsData(String pathString, final ValueEventListener callBack) {
    fbDB.child(pathString).addValueEventListener(callBack);
}

...

// final Map listOfUsers = ... ;
getSeasonsData("Seasons", new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {

        List<UserSeasons> usersSeasonsListTemp = new ArrayList<>();

        if (dataSnapshot.exists()) {

            for (DataSnapshot ds : dataSnapshot.getChildren()) {
                // For example...
                // Object someData = ds.getValue(...);
                // if (listOfUsers.containsKey(someData.getID())) { }

Here, I removed the second parameter because if you really need it, you can do "Seasons/" + key because Firebase supports path query strings

OneCricketeer
  • 179,855
  • 19
  • 132
  • 245
  • I understand that I should change the structure of the query or FB database. But while it's in this state, is there a quick fix to just wait for all callbacks to occur? – PSLDev Oct 25 '17 at 10:41
  • I've given you three alternative options here. I don't know how to write it in order to get the data you're expecting – OneCricketeer Oct 25 '17 at 13:49
0

The network call can take time so you don't expect the data to be ready before you create your RecycleView adapter. I suggest you setup your adapter with an empty dataset, then in the onSuccess callback you update your dataset and notify the adapter. You can do this by adding a new method to update the dataset in your RecyclerView adapter.

danijax
  • 69
  • 5