0

I need to fill a RecyclerView with the trip data. This means I need to retrieve data from two different collections from my Firestore database.

  1. collection 1: "users" which contains all data for each user
  2. collection 2: "trips" which contain all data for every trip

Inside each trip, the driverId is stored, using that driverId I want to retrieve more information about that user, so I will need two queries.

This means that I need a listener inside a listener and there are two problems with that.

To explain this in detail, here is my non-working :


public class Trip {
    private String attribute1, attribute2, etc.
    private User driver;
    ... constructor, getters, setters
}


public class User {
    private String id, username, email, etch.
    ... constructor, getters, setters



// Fragment class

private void retrieveTrips(){
        database.collection("trips")
                .get()
                .addOnCompleteListener(task -> {
                    if(task.isSuccessful() && task.getResult() != null)
                    {
                        Trips<Trip> = new ArrayList<>();
                        for(QueryDocumentSnapshot queryDocumentSnapshot : task.getResult()){
                            Trip trip = new Trip();
                            trip.setTripUid(queryDocumentSnapshot.getId());
                            trip.setStartTime(queryDocumentSnapshot.getString("startTime"));
                            trip.setStartLocation(queryDocumentSnapshot.getString("startLocation"));
                            // etc.
                            String driverId = queryDocumentSnapshot.getString("driverId");
        
                            // Now I need to retrieveUser driver data using driverId
                            Query driverQuery = database.collection("users").whereEqualTo("userId" , driverId);
                            driverQuery.get().addOnCompleteListener( task2 -> {
                                if (task2.isSuccessful() && !task2.getResult().isEmpty()) {
                                    for (QueryDocumentSnapshot document : task2.getResult()) {
                                        String driverUsername = document.getString("username");
                                        String driverEmail = document.getString("email");
                                        // etc.. many attributes
                                        
                                        trip.setDriver(new User(driverUid,driverUsername,driverEmail, etc...));
                                    }
                                } else if(task2.getResult().isEmpty())
                                {
                                    //Driver not found in Firestore
                                }
                                else {
                                    // task2 not successful
                                }}).addOnFailureListener(e -> // error));
                        }

                        if(!trips.isEmpty()){
                        // if trips list is not empty, set the adapter
                            TripAdapter tripAdapter = new TripAdapter(trips,this);
                            binding.availableTripsRecyclerView.setAdapter(tripAdapter);
                        } else{
                            // found no trips to show
                            showErrorMessage();
                        }
                    }
                    else{
                        // error !task.isSuccessful() or task.getResult() == null
                    }
                });

Problem 1) Both trip and trips list must be declared as final Lists and placed outside the listeners, as class attributes. Problem 2). Listeners run asynchronous to each other. The "outside" listener executes and finishes before the "nested" one even starts.

Using the final ArrayLists method to solve problem 1) already looks bad. What is the correct way to implement these nested queries?

Execution order example:

  1. Outside listener is called:
  2. Loop starts for each document result
  3. first trip's attributes are stored in a trip object.
  4. execution of outside listener is stopped and nested listener is called..
  5. User driver details are retrieved and stored in the same trip object.
  6. trip object is complete and can now be added inside the trips list
  7. nested listener is complete and execution of outside listener is continued..
  8. code executes the same for all documents retrieved.
  9. trips list is now complete and we can now set the adapter.

1 Answer 1

0

This means that I need a listener inside a listener.

What I see in your code, you're using Query#get() calls and not persistent listeners, which means that you read the data each time exactly once.

Since you're dealing with a NoSQL database, please note that there are no JOIN clauses here. So having a structure of two different collections for the purpose of querying them both might not be the best option in your case.

What I recommend, is to add all the data that you need, in the documents of a single collection, so you can query the data a single time. For instance, you might consider adding the trips that correspond to a user in the user document itself. This means that if you need to get all users along with all their data and trips, a single query is required. But such a solution will work only as long as the data inside the user document stays below the 1 Mib maximum limitation.

On the other hand, you might also consider taking a look at the Firebase Realtime Database, which has a different way of structuring the data and a totally different billing mechanism.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.