Java – Wait until the Firestore data is retrieved to start the activity

Wait until the Firestore data is retrieved to start the activity… here is a solution to the problem.

Wait until the Firestore data is retrieved to start the activity

I made a list of POJO objects that get values from my Cloud Firestore database. This list is a class in itself, and every time I create a new list, remember to retrieve the properties. The problem is that the list is not filled in the creation activity, but later at a later time. I’ve tried adding loops, tasks, waiting, but I don’t get the results I want. Here’s how I satisfactorily retrieved the data:

private void retrieveData(){
        reference = firestore.collection("attractions");
        reference.addSnapshotListener(new EventListener<QuerySnapshot>() {
            @Override
            public void onEvent(@Nullable QuerySnapshot queryDocumentSnapshots,
                                @Nullable FirebaseFirestoreException e) {
                attractionList.clear();
                for (DocumentSnapshot snapshot : queryDocumentSnapshots) {
                    Attraction attraction = snapshot.toObject(Attraction.class);
                    addAttractions(attraction);
                }
            }
        });
    }

I retrieve data every time I create a new object:

public AttractionList() {
        firestore.setFirestoreSettings(settings);
        reference = firestore.collection("attractions");
        attractionList = new ArrayList<>();
        retrieveData();
    }

Then, if I check the list size on my activity at creation time:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_attraction);
        Toolbar toolbar = findViewById(R.id.tb_attraction);
        setSupportActionBar(toolbar);

Creating POJO List and retrieving data
        attractionList = new AttractionList();
        Log.e("List Size: ", ""+attractionList.getAttractionList().size());
        List Size: 0
    }

But if I use the same log time after any button :

@Override
    public void onBackPressed(){
        Log.e("List Size: ", ""+attractionList.getAttractionList().size());
        List Size: 16
    }

I don’t really want to retrieve data in each activity or create intents to introduce objects between activities. Is there any way to fix this?

PS: This is my first article. Sorry if I didn’t do it the right way. Thank you so much.

Solution

You can’t use content that hasn’t been loaded yet. Firestore loads the data asynchronously, as this can take some time. Depending on your connection speed and status, it can take hundreds of milliseconds to seconds to get that data. If you want to pass a list of objects of type Attraction to another method, simply call the method in the onSuccess() method and pass the list as a parameter. So a quick workaround is:

FirebaseFirestore rootRef = FirebaseFirestore.getInstance();
CollectionReference attractionsRef = rootRef.collection("attractions");

attractionsRef.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
    @Override
    public void onComplete(@NonNull Task<QuerySnapshot> task) {
        if (task.isSuccessful()) {
            List<Attraction> attractionsList = new ArrayList<>();
            for (QueryDocumentSnapshot document : task.getResult()) {
                Attraction attraction = document.toObject(Attraction.class);
                attractionsList.add(attraction);
            }
            methodToProcessTheList(attractionsList);
        }
    }
});

Keep in mind that the onSuccess() method has asynchronous behavior, which means it is called even before you get the data from the database. If you want to use attractionsList outside of this method, you need to create your own callback. To do this, first you need to create an interface that looks like this:

public interface MyCallback {
    void onCallback(List<Attraction> attractionsList);
}

Then you need to create a method that actually gets the data from the database. This method should look like this:

public void readData(MyCallback myCallback) {
    attractionsRef.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
        @Override
        public void onComplete(@NonNull Task<QuerySnapshot> task) {
            if (task.isSuccessful()) {
                List<Attraction> attractionsList = new ArrayList<>();
                for (QueryDocumentSnapshot document : task.getResult()) {
                    Attraction attraction = snapshot.toObject(Attraction.class);
                    attractionsList.add(attraction);
                }
                myCallback.onCallback(attractionsList);
            }
        }
    });
}

Finally, just call the readData() method and pass an instance of the MyCallback interface as a parameter wherever needed, as follows:

readData(new MyCallback() {
    @Override
    public void onCallback(List<Attraction> attractionsList) {
        Do what you need to do with your list
    }
});

This is the only way you can use the attractionsList object outside of the onSuccess() method. For more information, you can also check out this video

Related Problems and Solutions