0

This is the result of RecyclerView:

enter image description here

Firestore database schema:

Frestore Data

this is the code I am using. This only retrieve data like in a image

db.collection("Expenses")
    .whereEqualTo("Email",email)
    .whereGreaterThanOrEqualTo("Date",MonthFirstDate)
    .whereLessThanOrEqualTo("Date",TodayDate)
    .orderBy("Date")
    .get()
    .addOnSuccessListener(new OnSuccessListener<QuerySnapshot>()                                            {
        @Override
        public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
            if (!queryDocumentSnapshots.isEmpty()){
                expensesArrayList.clear();
                List<DocumentSnapshot> list = queryDocumentSnapshots.getDocuments();
                for (DocumentSnapshot ds : list){
                    expenses expenses = ds.toObject(expenses.class);
                    expensesArrayList.add(expenses);
                }
                adapter.notifyDataSetChanged();
            }
        }
    })

recyclerview Adapter code

import java.util.ArrayList;

public class expensesAdapter extends RecyclerView.Adapter<expensesAdapter.expHolder> {

    private ArrayList<expenses> expensesArrayList;
    private Context context;


    public expensesAdapter(ArrayList<expenses> expensesArrayList, Context context) {
        this.expensesArrayList = expensesArrayList;
        this.context = context;
    }

    @NonNull
    @Override
    public expensesAdapter.expHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(context).inflate(R.layout.expensesitems,parent,false);
        return new expHolder(v);
    }

    @Override
    public void onBindViewHolder(@NonNull expensesAdapter.expHolder holder, int position) {
        expenses expenses = expensesArrayList.get(position);
        holder.catName.setText(expenses.getCategory());
        holder.catAmont.setText(expenses.getAmount());
    }

    @Override
    public int getItemCount() {
        return expensesArrayList.size();
    }

    class expHolder extends RecyclerView.ViewHolder {

        private final TextView catName,catAmont;

        public expHolder(@NonNull View itemView) {
            super(itemView);

            catName = itemView.findViewById(R.id.expCategory);
            catAmont =itemView.findViewById(R.id.expAmount);
        }
    }
}

This is the expenses class

public class expenses {
    String Category;
    String Date;
    String Email;
    String Amount;
    String Description;
    String expID;

    public expenses(String category, String date, String email, String amount, String description, String expID) {
        Category = category;
        Date = date;
        Email = email;
        Amount = amount;
        Description = description;
        this.expID = expID;
    }

    public expenses(){}

    public String getCategory() {
        return Category;
    }

    public void setCategory(String category) {
        Category = category;
    }

    public String getDate() {
        return Date;
    }

    public void setDate(String date) {
        Date = date;
    }

    public String getEmail() {
        return Email;
    }

    public void setEmail(String email) {
        Email = email;
    }

    public String getAmount() {
        return Amount;
    }

    public void setAmount(String amount) {
        Amount = amount;
    }

    public String getDescription() {
        return Description;
    }

    public void setDescription(String description) {
        Description = description;
    }

    public String getExpID() {
        return expID;
    }

    public void setExpID(String expID) {
        this.expID = expID;
    }
}

But i need this kind of result:

I want a category wise Sum amount on recyclerview. In my code its only showing separately.
Please gys help me for figure this out!

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193

2 Answers2

0

This is because for every document in the database collection, you are adding it to your expenses list. You can store all the expenses category wise and then add it to your adapter list.

...//other code

expensesArrayList.clear();
List<DocumentSnapshot> list = queryDocumentSnapshots.getDocuments();

// create a map for category wise total expense
Map<String,Integer> map = new HashMap<>();

for (DocumentSnapshot ds : list){
    expenses expenses = ds.toObject(expenses.class);
    String category = expenses.getCategory();
    int amount = Integer.parseInt(expenses.getAmount());
    if(map.containsKey(category))
        amount += map.get(category);
    map.put(category, amount);
}

// Iterate over map and add expense for each category
for (Map.Entry<String,Integer> entry : map.entrySet()){
    
    // add a constructor in your expenses class for this
    expenses categoryExpense = new expenses(entry.getKey(), String.valueOf(entry.getValue()));
    expensesArrayList.add(categoryExpense);
}
adapter.notifyDataSetChanged();

...// other code

// In you expenses.java class add this constructor as well
public expenses(String category, String amount) {
        Category = category;
        Amount = amount;
}

However, there can be problems with this implementation in case you have similar categories with different names like, phone and Phone, they will be considered different categories. I would suggest you to redesign your implementation with enum classes for categories.

Quick Tips:

  1. Class names should always be capitalized. So it should be Expense and not expenses.
  2. Member fields name shouldn't be capitalized, so they should be category, amount, email, etc.
  3. Make data type of your amount field as int or double, to avoid parsing and easier calculations.
  • I try this, but came a error with constructor in expenses class. what I need, want to get category wise Sum separately. Thank You! – Dulitha Bandaranayake Aug 08 '23 at 07:35
  • I have updated my answer as per your requirements. You will also have to add another constructor in your expenses class, or you can use the old one with null values for remaining fields. – Pawan Singh Harariya Aug 08 '23 at 10:47
  • `map.put(category, map.getOrDefault(category, 0) + Integer.parseInt(expenses.getAmount()));` have some error in this code. Error : `java.lang.NoSuchMethodError: No interface method getOrDefault(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; in class Ljava/util/Map; or its super classes (declaration of 'java.util.Map' appears in /system/framework/core-libart.jar)` – Dulitha Bandaranayake Aug 09 '23 at 04:50
  • I am not sure why map.getOrDefault() method is not working. You can use normal map.containsKey() method instead. I have updated the code accordingly. – Pawan Singh Harariya Aug 09 '23 at 06:58
  • This worked! Thank you very much your patience until resolved this. I am very beginner to the android just started, This help me lot. And thank you for your guidance I fixed those mistakes. blessing! – Dulitha Bandaranayake Aug 09 '23 at 10:14
0

The problem in your code lies in the fact that you have in your expenses class a field called Category and a getter called getCategory(), which is not correct since Firebase is looking in the database for a field named category and not Category. See the lowercase c letter vs. capital letter C?

The Firebase SDK maps between your Java object and the JSON properties in the Cloud Firestore based on JavaBean property naming conventions. That being said, there are multiple solutions to solve this problem, but here are the simplest ones:

  • Make the fields follow the naming convention of the properties:
public class Expenses {
    String category;
    String date;
    String email;
    String amount;
    String description;
    String expId;

    public expenses(String category, String date, String email, String amount, String description, String expId) {
        this.category = category;
        this.date = date;
        this.email = email;
        this.amount = amount;
        this.description = description;
        this.expId = expId;
    }

    public Expenses(){}

    public String getCategory() {
        return category;
    }

    public void setCategory(String category) {
        this.category = category;
    }

    public String getDate() {
        return date;
    }

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

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getAmount() {
        return Amount;
    }

    public void setAmount(String amount) {
        this.amount = amount;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getExpId() {
        return expId;
    }

    public void setExpId(String expId) {
        this.expId = expId;
    }
}
  • Use @PropertyName annotation in front of the getters, to map the the fields in your class into the name that exists inside the database:
public class expenses {
    String Category;
    String Date;
    String Email;
    String Amount;
    String Description;
    String expID;

    public expenses(String category, String date, String email, String amount, String description, String expId) {
        Category = category;
        Date = date;
        Email = email;
        Amount = amount;
        Description = description;
        this.expID = expID;
    }

    public expenses(){}

    @PropertyName("Category")
    public String getCategory() {
        return Category;
    }

    public void setCategory(String category) {
        Category = category;
    }

    @PropertyName("Date")
    public String getDate() {
        return Date;
    }

    public void setDate(String date) {
        Date = date;
    }

    @PropertyName("Email")
    public String getEmail() {
        return Email;
    }

    public void setEmail(String email) {
        Email = email;
    }

    @PropertyName("Amount")
    public String getAmount() {
        return Amount;
    }

    public void setAmount(String amount) {
        Amount = amount;
    }

    @PropertyName("Description")
    public String getDescription() {
        return Description;
    }

    public void setDescription(String description) {
        Description = description;
    }

    public String getExpID() {
        return expID;
    }

    public void setExpID(String expID) {
        this.expID = expID;
    }
}

P.S. Please also see that I have renamed the class from expenses to Expenses, to the Java Naming Conventions.

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193