Is there any way to make my app wait for data to be retrieved from Firebase before continuing the code?

203
July 20, 2019, at 9:40 PM

My app has to display a list of names on ListView. Those names are stored within Cloud Firestore in the following manner:

Collection: users - Documents: Organized by user UID - Field: name (I must note that there are other fields for each user too, however i need to retrieve the name field specifically)

To accomplish this, I have a first list that retrieves all documents or user UIDs. That first list is then used within a for loop to retrieve the name of each user in the users collection.

However, due to Firebase retrieving data asynchronously, some names are usually missing and they end up being displayed in a disorganized manner (not consistent with the order in which uids were passed from the first list).

If anyone could give me any insight on how to make Firebase wait for data to be retrieved before continuing with the for loop it would be greatly appreciated!

Below is some of my code to give you a better idea of what I am doing.

This first part of the code, which successfully retrieves all documents (uids) and puts them on a list

subTopicsDatabase.collection("schoolTopics").document(docKey).get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
                @Override
                public void onComplete(@NonNull Task<DocumentSnapshot> task) {
                    if (task.isSuccessful()){
            DocumentSnapshot document = task.getResult();
            if (document.exists()) {
                List<String> list = new ArrayList<>();
                Map<String, Object> map = document.getData();
                if (map != null) {
                    for (Map.Entry<String, Object> entry : map.entrySet()) {
                        list.add(entry.getValue().toString());
                    }
                }
                }});

The second part of the code, which doesnt work due to Firebase's asynchronous behavior.


for (int i = 0; i<list.size(); i++) {
                        String uid = list.get(i);
                        Toast.makeText(TutorsListActivity.this, uid, Toast.LENGTH_LONG).show();
                        subTopicsDatabase.collection("users").document(uid).get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
                            @Override
                            public void onSuccess(DocumentSnapshot documentSnapshot) {
                                if (documentSnapshot.exists()) {
                                    String stName = documentSnapshot.getString("name");
                                    ArrayAdapter<String> adapter = new ArrayAdapter<>(TutorsListActivity.this, R.layout.item_subtopic, testList);
                                    adapter.notifyDataSetChanged();
                                    sListView2.setAdapter(adapter);
                                }
                                }
                            });
                        }
Answer 1

You need to store the elements and in last of the for loop, you have to show the names list.

As you said you are getting a list of All UID's now you want their names on a list. I had updated your code to work.

// Create a Hashmap Object which has Key as UID and Name as Key    
HashMap<String,String> hashMap = new HashMap<>();

for (int i = 0; i<list.size(); i++) {
        final String uid = list.get(i);
        Toast.makeText(TutorsListActivity.this, uid, Toast.LENGTH_LONG).show();
        subTopicsDatabase.collection("users").document(uid).get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
            @Override
            public void onSuccess(DocumentSnapshot documentSnapshot) {
                if (documentSnapshot.exists()) {
                    //Store Your UID and Name in Hashmap
                    String stName = documentSnapshot.getString("name");
                    hashMap.put(uid,stName);
                }
                //Check if it is last index of array then show the names list
                if(i==list.size()-1){
                    showListInAdapter(hashMap);
                }
            }
        });
    }
    private void showListInAdapter(HashMap<String,String> hashMap) {
        //now convert your hashmap into a list of name  and get Your Names List and show in Adapter
        ArrayList<String> listOfNames = new ArrayList<>(hashMap.keySet());
        //Set list to Adapter
        ArrayAdapter<String> adapter = new ArrayAdapter<>(TutorsListActivity.this, R.layout.item_subtopic, listOfNames);
        sListView2.setAdapter(adapter);
        adapter.notifyDataSetChanged();
    }
Answer 2

Your assertion that it doesn't work because of Firebase's asynchronous behavior is incorrect. The reason that your view is not displaying the way you want it to, is because you are updating your adapter every single time you receive a document back from Firebase.

In psuedocode, this is what should happen:

// Create function with completion block - i.e. fetchTopicNames
//
// Create array to hold fetched String values - i.e. topicNames
// For loop to request each document
//    add String value to `topicNames`
//    if current iteration is last iteration, finish forLoop and return topicNames
//

In another method, call your newly created method, update your adapter with your full list of topicNames. You can also then perform other operations on your Array like filtering and sorting. There is probably a more efficient way as well, I'm just giving you the most basic way to accomplish your task.

Answer 3

I recommend that you follow the Guide to App Architecture and use a LiveData observer to keep the ListView updated. You can follow this tutorial and insert your Firebase access in the Repository class.

Changing your code to fit the MVVM pattern may require a bit of work but it will also make your app run better and simplify some development later.

Answer 4

you can simulate fetching user synchronous by making recursion (function which call it self until index becomes bigger then size of list of uids).

So first thing you want to define adapter and List of strings (which represent user names). When you do that, you can call recursion, which will populate your List and notifyDataSetChanged. Here is the example

// Define empty list of user names, which you will populate later with recursion
List<String> userNames = new ArrayList<String>();
// Connect adapter with empty list
ArrayAdapter<String> adapter = new ArrayAdapter<>(TutorsListActivity.this, R.layout.item_subtopic, userNames);
// Set adapter to ListView
sListView2.setAdapter(adapter);
// Call recursion with list of uids and starting index of 0
getUserSync(list, 0);

private void getUserSync(List<String> list, int i) {
    if (i < 0 || i > list.length - 1) {
        // If index i is out of bounds for list, we break the recursion
        return;
    }
    String uid = list.get(i);
    Toast.makeText(TutorsListActivity.this, uid, Toast.LENGTH_LONG).show();
    subTopicsDatabase.collection("users").document(uid).get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
        @Override
        public void onSuccess(DocumentSnapshot documentSnapshot) {
            // When we load document, we fetch name and add it to the list which is connected to adapter
            // After that, we call adapter.notifyDataSetChanged which will update ui
            // When all that is done, we call getUserSync, to fetch user name for next uid
            if (documentSnapshot.exists()) {
                String stName = documentSnapshot.getString("name");
                if (stName != null) {
                    userNames.add(stName);
                    adapter.notifyDataSetChanged();
                }
            }
            getUserSync(list, i++);
        }
    }).addOnFailureListener(new OnFailureListener() {
        @Override
        public void onFailure(@NonNull Exception e) {
            // If enything goes wrong, we break the recursion
            return;
        }
    });
}

If you have any troubles, feel free to comment..

READ ALSO
Is there a RN Number Picker working on android as a wheel?

Is there a RN Number Picker working on android as a wheel?

I'm building a React Native app, and I want my user to input a number of player and I ant to to be intuitive, i want a weel instead of writing it with the keyboeard

187
Populate Spinner with another spinner in RecyclerView Adapter

Populate Spinner with another spinner in RecyclerView Adapter

I have a recyclerView in my App, populating it with a list of student's data and I have Edit and Delete button attached to it, I have worked out the delete button, but Edit button has a popup, where a user can edit record of student's data, it has two spinners...

179
How to bind a virtual display as an external opengl es texture?

How to bind a virtual display as an external opengl es texture?

I am trying to render the result of a virtualDisplay as a texture in opengl es to apply a shader on itAll of this is done in a background service

112
Listen to Firebase DB changes in background of Android app

Listen to Firebase DB changes in background of Android app

I'm creating my first Android app which is a Tinder clone just for learning purposes

190