0

I am coding an android app using Google's FireStore backend. The code to grab all the documents in a firestore collection is as follows, from the official documentation:

db.collection("cities")
    .get()
    .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
        @Override
        public void onComplete(@NonNull Task<QuerySnapshot> task) {
            if (task.isSuccessful()) {
                for (QueryDocumentSnapshot document : task.getResult()) {
                    Log.d(TAG, document.getId() + " => " + document.getData());
                }
            } else {
                Log.d(TAG, "Error getting documents: ", task.getException());
            }
        }
    });

Where the above code outputs to Log.d(...), I would like to have my program add the results of the document.getData() call to an ArrayList accessible outside the inner class/method. I'm not sure what is the best way to do this. Attempting to change the return the return type of the onComplete method yields errors. Is there a standard way of accessing elements in methods like this?

Declaring a variable and trying to mutate within the class also isn't possible, unless the variable is final, which defeats the point.

Chris T
  • 453
  • 1
  • 6
  • 17

2 Answers2

1

I would like to have my program add the results of the document.getData() call to an ArrayList accessible outside the inner class/method.

In this case, if you are using a model class, you should use toObject() method and add objects of type YourModelClass in an ArrayList like this:

if (task.isSuccessful()) {
    List<YourModelClass> list = new ArrayList<>();
    for (QueryDocumentSnapshot document : task.getResult()) {
        YourModelClass yourModelClass = document.toObject(YourModelClass.class);
        list.add(yourModelClass);

        //Do what you need to do with your list
    }
}

As you can see, I advised you to use the list of YourModelClass objects inside the callback. This is because onComplete() method has an asynchronous behavior and you cannot simply use that list outside the callback because it will always be empty.

Attempting to change the return the return type of the onComplete method yields errors.

Changing the return type of the method will not give you errors but the result will always be an empty list. You cannot return something now that hasn't been loaded yet. A quick solve for this problem would be to use that list of objects only inside the onComplete() method, as I already wrote above, or if you want to use it outside, I recommend you see the last part of my anwser from this post in which I have explained how it can be done using a custom callback. You can also take a look at this video for a better understanding.

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • You can also take a look **[here](https://stackoverflow.com/questions/50650224/wait-until-firestore-data-is-retrieved-to-launch-an-activity/50680352)**, for a better understanding. – Alex Mamo Oct 30 '18 at 04:36
1

That is an asynchronous call (it launches a background process to do the Firebase query, and once that is done it executes your onComplete listener), so you can't expect to have the data in hand immediately after making the database call. For example, if your function looks like

void getData() {
    final List<MyData> list = new ArrayList<>();

    db.collection("cities")
        .get()
        .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
            @Override
            public void onComplete(@NonNull Task<QuerySnapshot> task) {
                if (task.isSuccessful()) {
                    for (QueryDocumentSnapshot document : task.getResult()) {
                        Log.d(TAG, document.getId() + " => " + document.getData());
                        list.add(new MyData(document.getData()));
                    }
                } else {
                    Log.d(TAG, "Error getting documents: ", task.getException());
                }
            }
        });

    Log.d(TAG, "List size = " + list.size()); // will print 0
    // list will be empty here, the firebase call will take hundreds 
    // to thousands of milliseconds to complete
}

You need to structure your program so that it can wait for the data to arrive. There are a couple ways you could do this. One would be to have list be a class member that gets filled by the onComplete listener (then you have to structure the program to handle data coming in at random times).

Another way would be to have a data handler routine that takes the ArrayList and does something with it. This method could be called from the onComplete listener once you've gotten all the data. For example:

void getData() {
    db.collection("cities")
        .get()
        .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
            @Override
            public void onComplete(@NonNull Task<QuerySnapshot> task) {
                if (task.isSuccessful()) {
                    List<MyData> list = new ArrayList<>();
                    for (QueryDocumentSnapshot document : task.getResult()) {
                        Log.d(TAG, document.getId() + " => " + document.getData());
                        list.add(new MyData(document.getData()));
                    }
                    processData(list);
                } else {
                    Log.d(TAG, "Error getting documents: ", task.getException());
                }
            }
        });
}

void processData(List<MyData> data) {
    // do stuff with the data, or make a copy, or otherwise share it
    // with the rest of your program
}
Tyler V
  • 9,694
  • 3
  • 26
  • 52