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.
- collection 1: "users" which contains all data for each user
- 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:
- Outside listener is called:
- Loop starts for each document result
- first trip's attributes are stored in a trip object.
- execution of outside listener is stopped and nested listener is called..
- User driver details are retrieved and stored in the same trip object.
- trip object is complete and can now be added inside the trips list
- nested listener is complete and execution of outside listener is continued..
- code executes the same for all documents retrieved.
- trips list is now complete and we can now set the adapter.