Context
I've been attempting to display data from Firestore in a FirestoreRecyclerAdapter
, however this data is basically nested Maps, so a direct approach using the Query
function is not ideal. Here is an image of my data structure:
Notice that ServiceOrder
, client
and vehicle
are all Maps. In my Java code, ServiceOrder
is made up of a Client
and Vehicle
objects.
So, if I were to use .setQuery(query, ServiceOrder.class)
, it would attempt to Map all of the data into ServiceOrder
objects. But since my document is structured the way it is, that is not possible.
Issue
I suppose this could be fixed by mapping all documents into an object of a new class, similar to what is done here: https://medium.com/firebase-tips-tricks/how-to-map-an-array-of-objects-from-cloud-firestore-to-a-list-of-objects-122e579eae10.
Even though I can see how it could be done using a normal RecyclerView and using a custom adapter, could the same solution be used in FirestoreRecyclerAdapter
? Because I did try to create something akin to the solution in the link, but couldn't get it to work.
My code
Here is where I'm setting up the RecyclerView and Querying the data from Firestore:
private void setupRecyclerView() {
RecyclerView recyclerView = findViewById(R.id.recyclerViewOs);
Query query = osRef.orderBy("ServiceOrder",
Query.Direction.ASCENDING); //This is the issue.
//How could I map the documents here?
FirestoreRecyclerOptions<ServiceOrder> options =
new FirestoreRecyclerOptions.Builder<ServiceOrder>()
.setQuery(query, ServiceOrder.class)
.build();
listAdapter = new FirestoreAdapter(options);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(listAdapter);
}
My FirestoreRecyclerAdapter, where I'm binding my Views. The onBindViewHolder
returns NPE for every View. This is the problem with the nested Maps described early.
public class FirestoreAdapter extends FirestoreRecyclerAdapter<ServiceOrder, FirestoreAdapter.ViewHolder> {
@Override
protected void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull ServiceOrder model) {
holder.osIdItem.setText(String.valueOf(model.getId()));
holder.osClientItem.setText(model.getPaymentForm());
holder.osDateItem.setText(model.getPaymentForm());
holder.osValueItem.setText(String.valueOf(model.getTotalValue()));
}
And finally, my ServiceOrder
class. Getters/Setters were removed to increase readability.
public class ServiceOrder {
public Client client;
public Vehicle vehicle;
private String service;
private String observation;
private String paymentForm;
private String date;
private double totalValue;
private int id;
public ServiceOrder() {
}
private ServiceOrder(ServiceOrderBuilder serviceOrderBuilder){
this.client = serviceOrderBuilder.client;
this.vehicle = serviceOrderBuilder.vehicle;
this.service = serviceOrderBuilder.service;
this.paymentForm = serviceOrderBuilder.paymentForm;
this.observation = serviceOrderBuilder.observation;
this.totalValue = serviceOrderBuilder.value;
this.date = serviceOrderBuilder.date;
}
public static class ServiceOrderBuilder {
private Vehicle vehicle;
private Client client;
private final String service;
private final String paymentForm;
private final int id;
private final double value;
private final String date;
private String observation;
public ServiceOrderBuilder(Client client, Vehicle vehicle,
String service, String paymentForm,
int id, double value, String date) {
this.client = client;
this.vehicle = vehicle;
this.service = service;
this.paymentForm = paymentForm;
this.id = id;
this.value = value;
this.date = date;
}
public ServiceOrder.ServiceOrderBuilder observation(String observation) {
this.observation = observation;
return this;
}
public ServiceOrder build() {
ServiceOrder serviceOrder = new ServiceOrder(this);
return serviceOrder;
}
}
}
My attempt
As suggested in another post, I attempted to create a new ServiceOrderDocument
in order to map all documents into an object of this class. The class:
public class ServiceOrderDocument {
ServiceOrder serviceOrder;
public ServiceOrderDocument() {}
public ServiceOrderDocument(ServiceOrder serviceOrder) {
this.serviceOrder = serviceOrder;
}
@PropertyName("ServiceOrder")
public ServiceOrder getServiceOrder() {
return serviceOrder;
}
}
Ànd pass this into the Adapter found in the private void setupRecyclerView()
. However, the Adapter expects a QuerySnapshot
, so I feel like I'm stuck here.
Reproducing the issue
If you'd like to try it out yourself, the best way would be to have three Classes, with one of them having objects from the other two. A example would be a Sale
class having objects from Salesman
and Product
.
Proceed to write a Sale
object into your Firestore database, and see how it creates a nested document. Then, try to display that Sale
in a RecyclerView using FirestoreRecyclerAdapter. Your onBindViewHolder
should have a Sale
model that would get the data from it's getters.
Edit
So using a List to get the content seems to work at a first glance, by using Cast
I could pass it as a adapter for the FirestoreRecyclerAdapter
, however, it does not work for the startListening()
methods. Here's what I did:
private void setupRecyclerView() {
docRef.get().addOnCompleteListener(task -> {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
services = document.toObject(ServiceOrderDocument.class).services;
RecyclerView recyclerView = findViewById(R.id.recyclerViewOs);
Query query = osRef.orderBy("ServiceOrder",
Query.Direction.ASCENDING);
FirestoreRecyclerOptions<ServiceOrder> options =
new FirestoreRecyclerOptions.Builder<ServiceOrder>()
.setQuery(query, ServiceOrder.class)
.build();
// listAdapter = new FirestoreAdapter(options);
services = (List<ServiceOrder>) new FirestoreAdapter(options);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter((RecyclerView.Adapter) services);
}
}
});
}
However, the following issue is created:
@Override
protected void onStart() {
super.onStart();
listAdapter.startListening();//NPE Error
services.startListening();//Can't use method
}