Why does data from Firestore DB returns as 'null' in FirestoreRecyclerAdapter?

Viewed 85

After being successful with writing and reading data from Firestore, I am now trying to display that data in a RecyclerView by utilizing FirestoreRecyclerAdapter. However, in my onBindViewHolder, where I'm trying to set a textView with data from the database, I'm getting a NullPointerException, where my Client name is null.

Here is my FirestoreAdapter class:

public class FirestoreAdapter extends FirestoreRecyclerAdapter<ServiceOrder, FirestoreAdapter.ViewHolder> {

    public FirestoreAdapter(@NonNull FirestoreRecyclerOptions<ServiceOrder> options) {
        super(options);
    }

    @Override
    protected void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull ServiceOrder model) {
        holder.osIdItem.setText(String.valueOf(getItem(position).getId()));
        holder.osClientItem.setText(getItem(position).getClient().getName()); //Line with error
        holder.osDateItem.setText(getItem(position).getDate());
        holder.osValueItem.setText(String.valueOf(getItem(position).getTotalValue()));
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

        LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());

        View view = layoutInflater.inflate(R.layout.layout_os_item, parent, false);

        return new FirestoreAdapter.ViewHolder(view);
    }

    public class ViewHolder extends RecyclerView.ViewHolder {

        private  TextView osIdItem;
        private  TextView osClientItem;
        private  TextView osDateItem;
        private  TextView osValueItem;

        public ViewHolder(View itemView) {
            super(itemView);

            osIdItem =  itemView.findViewById(R.id.osIDItem);
            osClientItem =   itemView.findViewById(R.id.osClientNameItem);
            osDateItem =  itemView.findViewById(R.id.osDateItem);
            osValueItem =  itemView.findViewById(R.id.osValueItem);
        }
    }
}


Here is my Logcat

 2022-09-09 09:58:38.479 1147-1147/com.leonardomaito.autocommobile E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.leonardomaito.autocommobile, PID: 1147
    java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String com.leonardomaito.autocommobile.models.Client.getName()' on a null object reference
        at com.leonardomaito.autocommobile.adapters.FirestoreAdapter.onBindViewHolder(FirestoreAdapter.java:27)
        at com.leonardomaito.autocommobile.adapters.FirestoreAdapter.onBindViewHolder(FirestoreAdapter.java:18)
        at com.firebase.ui.firestore.FirestoreRecyclerAdapter.onBindViewHolder(FirestoreRecyclerAdapter.java:157)
        at androidx.recyclerview.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:7254)
        at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:7337)
        at androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:6194)
        at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6460)
        at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6300)
        at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6296)
        at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2330)
        at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1631)
        at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1591)
        at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:668)
        at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4309)
        at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:4012)
        at androidx.recyclerview.widget.RecyclerView.consumePendingUpdateOperations(RecyclerView.java:2028)
        at androidx.recyclerview.widget.RecyclerView$1.run(RecyclerView.java:417)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:911)
        at android.view.Choreographer.doCallbacks(Choreographer.java:723)
        at android.view.Choreographer.doFrame(Choreographer.java:655)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:897)
        at android.os.Handler.handleCallback(Handler.java:789)
        at android.os.Handler.dispatchMessage(Handler.java:98)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6541)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)

My Client class is using a Builder design pattern, along with the default constructor as required by Firestore, and a Parcelable for transfering data through Activities, I'm only adding this since I'm not sure if it could impact how Firestore is reading my data.

So, how come I can use read and write data to the database, but I can't seem to display it? I have a Client 'name' in my database, shouldn't it be displaying that?

If it helps, my ServiceOrder class requires a Vehicle and a Client object in order to be built.

This is how my data is structured in Firestore:

Part1

Part2

Thank you.

EDIT

As requested, the Firestore Reference:

public class OsActivity extends AppCompatActivity {

    private FirestoreAdapter listAdapter;
    private FirebaseFirestore db = FirebaseFirestore.getInstance();
    private CollectionReference osRef = db.collection("cliente");

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_os);


        setupRecyclerView();

        }

        private void setupRecyclerView() {

            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);


            recyclerView.setHasFixedSize(true);
            recyclerView.setLayoutManager(new LinearLayoutManager(this));
            recyclerView.setAdapter(listAdapter);
        }

    @Override
    protected void onStart() {
        super.onStart();
        listAdapter.startListening();
    }

    @Override
    protected void onStop() {
        super.onStop();
        listAdapter.stopListening();
    }

Ran another debugger and got the following message No setter/field for ServiceOrder found on class com.leonardomaito.autocommobile.models.ServiceOrder

Database Schema

Firebase Console

ServiceOrder Class


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 String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }

    public double getTotalValue() {
        return totalValue;
    }

    public void setTotalValue(double totalValue) {
        this.totalValue = totalValue;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

   public Client getClient() {
        return client;
    }

    public void setClient(Client client) {
        this.client = client;
    }

    public Vehicle getVehicle() {
        return vehicle;
    }

    public void setVehicle(Vehicle vehicle) {
        this.vehicle = vehicle;
    }

    public String getService() {
        return service;
    }

    public void setService(String service) {
        this.service = service;
    }

    public String getObservation() {
        return observation;
    }

    public void setObservation(String observation) {
        this.observation = observation;
    }

    public String getPaymentForm() {
        return paymentForm;
    }

    public void setPaymentForm(String paymentForm) {
        this.paymentForm = paymentForm;
    }

    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;

        }
    }
}



1 Answers

Since you're using the Firebase-UI library, when you're using the following method call:

.setQuery(query, ServiceOrder.class)

It means that you're trying to map all documents that are returned by the query into objects of type ServiceOrder. But this is actually not possible because your actual document looks like this:

clienteTeste (document)
  |
  --- ServiceOrder (map)
        |
        --- client: {...} (map)
        |
        --- date: "09/09/2022"
        |
        --- //other fields

Instead of this:

clienteTeste (document)
  |
  --- client: {...} (map)
  |
  --- date: "09/09/2022"
  |
  --- //other fields

See, there is no ServiceOrder extra object? This would be the simplest solution for solving this issue, in which the fields should be added directly into the document not inside a map.

If you however cannot change the document structure, which I doubt, since there is only one (test) document present there, then you should consider using encapsulation by creating another class with a single field called ServiceOrder which should be of type ServiceOrder. And then map each document into an object of this new class.

Edit:

According to your last comment, at the minimum, the class should look like this:

public class ServiceDocument {
    private ServiceOrder serviceOrder;

    public ServiceDocument() {}

    public ServiceDocument(ServiceOrder serviceOrder) {
        this.serviceOrder = serviceOrder;
    }

    @PropertyName("ServiceOrder")
    public getServiceOrder() {
        return serviceOrder;
    }
}

Now you should map all documents into an object of type ServiceDocument.

Related