Open In App

How to Build a Simple Expense Calculator App in Android?

Improve
Improve
Like Article
Like
Save
Share
Report

Pre-requisites:

A simple expense calculator lets you add income and expenditures in a simplified manner. This is a glimpse of the application we are going to build. The application contains a single Activity with a RecyclerView, two EditTexts (one to enter amount and the other to enter a short note of the transaction), 1 clickable TextView to specify loss or gain, 1 clickable image to add the transaction to RecyclerView, and finally a custom ActionBar to show the balance. It includes Shared Preferences to store the data locally. A sample video is given below to get an idea about what we are going to do in this article. Note that we are going to implement this project using the Java language. 

Step by Step Implementation

Step 1: Create a New Project

To create a new project in Android Studio please refer to How to Create/Start a New Project in Android Studio. Note that select Java as the programming language.

Step 2:

Before moving to the coding section let’s add the necessary dependencies. The only dependency we have to add for the project is for Gson. It is a Java library that can be used to convert Java Objects into their JSON representation. It can also be used to convert a JSON string to an equivalent Java object. Go to app-level build.gradle file and add the following dependency and click on sync now.

implementation 'com.google.code.gson:gson:2.8.6'

Here is a reference,

Step 3:

Let’s add the necessary vector assets and drawable resource files. Go to app > res > drawable and add the following xml files. 

ic_delete.xml (Delete Icon)

XML




    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24"
    android:tint="?attr/colorControlNormal">
  <path
      android:fillColor="@android:color/white"
      android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
</vector>


Preview:

ic_send.xml (Send Icon)

XML




    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24"
    android:tint="?attr/colorPrimarySurface"
    android:autoMirrored="true">
  <path
      android:fillColor="@color/white"
      android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/>
</vector>


Preview:

ic_balance.xml (Wallet Icon)

XML




<vector android:height="24dp" android:tint="#FFFFFF"
    android:viewportHeight="24" android:viewportWidth="24"
    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
    <path android:fillColor="@android:color/white" android:pathData="M21,18v1c0,1.1 -0.9,2 -2,2L5,21c-1.11,0 -2,-0.9 -2,-2L3,5c0,-1.1 0.89,-2 2,-2h14c1.1,0 2,0.9 2,2v1h-9c-1.11,0 -2,0.9 -2,2v8c0,1.1 0.89,2 2,2h9zM12,16h10L22,8L12,8v8zM16,13.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z"/>
</vector>


Preview:

etbg.xml (Selector for Edit Text)

XML




<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:state_focused="false">
      <shape android:shape="rectangle">
          <corners android:radius="3dp"/>
          <stroke android:color="#DDD" android:width="2dp"/>
      </shape>
  </item>
    <item android:state_focused="true">
        <shape android:shape="rectangle">
            <corners android:radius="3dp"/>
            <stroke android:color="@color/purple_500" android:width="2dp"/>
        </shape>
    </item>
</selector>


Here is a screenshot for reference.

Step 4:

Now let’s add layout resource files for the custom ActionBar and RecyclerView row layout. Go to app > res > layout and add the following xml files. Below is the code for the custom_action_bar.xml file. 

XML




<?xml version = "1.0" encoding = "utf-8"?>
<LinearLayout 
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/purple_500"
    android:gravity="center_vertical"
    android:paddingStart="5dp"
    android:paddingTop="10dp"
    android:paddingEnd="5dp"
    android:paddingBottom="10dp">
  
    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_marginEnd="10dp"
        android:text="Expense Calculator"
        android:textColor="@color/white"
        android:textSize="20sp"
        android:textStyle="bold" />
  
    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_marginStart="10dp"
        android:layout_marginEnd="10dp"
        app:srcCompat="@drawable/ic_balance" />
  
    <TextView
        android:id="@+id/tvBalance"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:gravity="start"
        android:text="0.00"
        android:textColor="@color/white"
        android:textSize="20sp" />
    
</LinearLayout>


Preview:

Below is the code for the transaction_row_layout.xml file. (RecyclerView Row Layout)

XML




<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:cardBackgroundColor="#000000"
    app:cardUseCompatPadding="true">
  
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/white"
        android:orientation="horizontal"
        android:padding="5dp">
  
        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="6"
            android:orientation="vertical">
  
            <TextView
                android:id="@+id/tvAmount"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Amount"
                android:textColor="@color/black"
                android:textSize="24sp"
                android:textStyle="bold" />
  
            <TextView
                android:id="@+id/tvMessage"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Message"
                android:textColor="@color/black" />
  
        </LinearLayout>
  
        <ImageView
            android:id="@+id/ivDelete"
            android:layout_width="0dp"
            android:layout_height="32dp"
            android:layout_gravity="center"
            android:layout_weight="1"
            android:clickable="true"
            app:srcCompat="@drawable/ic_delete"
            tools:ignore="SpeakableTextPresentCheck,TouchTargetSizeCheck" />
    </LinearLayout>
  
  
</androidx.cardview.widget.CardView>


Preview:

Here is a screenshot for reference.

Step 5:

We have added the necessary resource files for the application we are building. Now, Let’s design the UI for our application. Add this xml file to app > res > layout. Below is the code for the activity_main.xml file. 

 

XML




<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
  
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rvTransactions"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_dodgeInsetEdges="bottom">
  
    </androidx.recyclerview.widget.RecyclerView>
  
    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        app:cardBackgroundColor="#DDD"
        app:cardPreventCornerOverlap="false"
        app:cardUseCompatPadding="false"
        app:layout_dodgeInsetEdges="bottom">
  
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="1dp"
            android:background="#FFFFFF"
            android:orientation="horizontal"
            android:padding="10dp">
  
            <TextView
                android:id="@+id/tvSign"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:clickable="true"
                android:gravity="center"
                android:text="+₹"
                android:textColor="#00c853"
                android:textSize="24sp"
                tools:ignore="TouchTargetSizeCheck" />
  
            <EditText
                android:id="@+id/etAmount"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_marginStart="2dp"
                android:layout_marginEnd="2dp"
                android:layout_weight="3"
                android:background="@drawable/etbg"
                android:ems="10"
                android:hint="Amount"
                android:inputType="number"
                android:maxLength="7"
                android:padding="5dp"
                tools:ignore="SpeakableTextPresentCheck,TouchTargetSizeCheck" />
  
            <EditText
                android:id="@+id/etMessage"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_marginStart="2dp"
                android:layout_marginEnd="2dp"
                android:layout_weight="7"
                android:background="@drawable/etbg"
                android:ems="10"
                android:hint="Message"
                android:inputType="textPersonName"
                android:maxLength="50"
                android:maxLines="1"
                android:padding="5dp"
                tools:ignore="SpeakableTextPresentCheck,TouchTargetSizeCheck" />
  
            <ImageView
                android:id="@+id/ivSend"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:clickable="true"
                app:srcCompat="@drawable/ic_send"
                tools:ignore="TouchTargetSizeCheck,SpeakableTextPresentCheck" />
        </LinearLayout>
    </androidx.cardview.widget.CardView>
  
    <TextView
        android:id="@+id/tvEmpty"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="10dp"
        android:layout_marginTop="10dp"
        android:layout_marginEnd="10dp"
        android:gravity="center"
        android:text="No transactions found!"
        android:textColor="@color/black"
        android:textSize="20sp" />
  
</androidx.coordinatorlayout.widget.CoordinatorLayout>


Preview:

Step 6:

We have added every resource file required for this application. Now let’s jump into the coding part. Let’s first create the Model Class for each transaction to be updated in the RecyclerView. It should contain four members as follows.

  • Amount (of type Integer)
  • Message (a short note about the Transaction)
  • Positive (a boolean variable to check if the amount is received ( Positive = true ) or spent (Positive = False)

Here is the Java code for the Model Class along with the corresponding constructor, getters, and setters. Below is the code for the TransactionClass.java file. (Model Class)

Java




package com.cs.expensecalculator;
  
public class TransactionClass {
    private int amount;
    private String message;
    private boolean positive;
  
    public TransactionClass(int amount, String message, boolean positive) {
        this.amount = amount;
        this.message = message;
        this.positive = positive;
    }
  
    public int getAmount() {
        return amount;
    }
  
    public void setAmount(int amount) {
        this.amount = amount;
    }
  
    public String getMessage() {
        return message;
    }
  
    public void setMessage(String message) {
        this.message = message;
    }
  
    public boolean isPositive() {
        return positive;
    }
  
    public void setPositive(boolean positive) {
        this.positive = positive;
    }
}


Step 7:

Now it’s time to create an Adapter for this Model Class. Let’s create a Class named TransactionAdapter that extends RecyclerView Adapter, and implement the necessary methods. If you’re new to RecyclerView, check out this article before proceeding. Below is the code for the TransactionAdapter.java file. Comments are added inside the code to understand the code in more detail.

Java




package com.cs.expensecalculator;
  
import static com.cs.expensecalculator.MainActivity.calculateBalance;
import static com.cs.expensecalculator.MainActivity.checkIfEmpty;
import static com.cs.expensecalculator.MainActivity.setBalance;
  
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
  
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
  
import java.util.ArrayList;
  
public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.TViewHolder> {
    Context ctx;
    
    // List containing data for recyclerview
    ArrayList<TransactionClass> transactionList;
  
    // Constructor for TransactionAdapter
    public TransactionAdapter(Context ctx, ArrayList<TransactionClass> transactionList) {
        this.ctx = ctx;
        this.transactionList = transactionList;
    }
  
    // On Create View Holder to Inflate transaction row layout
    @NonNull
    @Override
    public TransactionAdapter.TViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(ctx).inflate(R.layout.transaction_row_layout,parent,false);
        return new TransactionAdapter.TViewHolder(v);
    }
  
    @Override
    public void onBindViewHolder(@NonNull TransactionAdapter.TViewHolder holder, int position) {
          
          // Setting Message to a TextView in Row Layout
        holder.tvMessage.setText(transactionList.get(holder.getAdapterPosition()).getMessage());
         
          // If the transaction is Positive (Received Money) set Text Color to Green
        if(transactionList.get(holder.getAdapterPosition()).isPositive())
        {
            holder.tvAmount.setTextColor(Color.parseColor("#00c853"));
            // Setting Amount to a TextView in the row layout
            holder.tvAmount.setText("+₹"+Integer.toString(transactionList.get(holder.getAdapterPosition()).getAmount()));
        }
        
        // If the transaction is Negative (Spent Money) set Text Color to Red
        else {
            holder.tvAmount.setTextColor(Color.parseColor("#F44336"));
             
              // Setting Amount to a TextView in the row layout
            holder.tvAmount.setText("-₹"+Integer.toString(transactionList.get(holder.getAdapterPosition()).getAmount()));
        }
          
        // On Click Listener for Delete Icon
        holder.ivDelete.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // Confirmation Alert to delete a Transaction
                AlertDialog dialog = new AlertDialog.Builder(ctx)
                        .setCancelable(false)
                        .setTitle("Are you sure? The transaction will be deleted.")
                        .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialogInterface, int i) {
                                dialogInterface.dismiss();
                            }
                        })
                        .setPositiveButton("Ok", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialogInterface, int i) {
                                transactionList.remove(holder.getAdapterPosition());
                                dialogInterface.dismiss();
                                notifyDataSetChanged();
                                checkIfEmpty(getItemCount());
                                setBalance(transactionList);
                            }
                        })
                        .create();
                dialog.show();
            }
        });
    }
  
  
    // To get size of the list
    @Override
    public int getItemCount() {
        return transactionList.size();
    }
  
    // View Holder for a Transaction
    public static class TViewHolder extends RecyclerView.ViewHolder{
        TextView tvAmount,tvMessage;
        ImageView ivDelete;
  
        public TViewHolder(@NonNull View itemView) {
            super(itemView);
            tvAmount = itemView.findViewById(R.id.tvAmount);
            tvMessage = itemView.findViewById(R.id.tvMessage);
            ivDelete = itemView.findViewById(R.id.ivDelete);
        }
    }
}


Step 8:

We have defined the Model Class and the associated Adapter Class for Recycler View. Now it’s time to initialize everything in MainActivity, and implement functions to set custom action bar, set balance, and store and retrieve data locally using shared preferences in activity’s onStop() and onCreate() methods respectively. Here is the complete code for MainActivity. Below is the code for the MainActivity.java file. Comments are added inside the code to understand the code in more detail.

Java




package com.cs.expensecalculator;
  
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
  
import android.content.SharedPreferences;
import android.graphics.Color;
import android.os.Bundle;
import android.provider.CalendarContract;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
  
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
  
import java.lang.reflect.Type;
import java.util.ArrayList;
  
public class MainActivity extends AppCompatActivity {
    TextView tvSign;
    public static TextView tvEmpty, tvBalance;
    EditText etAmount, etMessage;
    ImageView ivSend;
    boolean positive = true;
    RecyclerView rvTransactions;
    TransactionAdapter adapter;
    ArrayList<TransactionClass> transactionList;
  
    // On create method
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
          
        // Function to initialize views
        initViews();
         
          // Function to load data from shared preferences
        loadData();
          
          // Function to set custom action bar
        setCustomActionBar();
          
          // To check if there is no transaction
        checkIfEmpty(transactionList.size());
  
        // Initializing recycler view
        rvTransactions.setHasFixedSize(true);
        rvTransactions.setLayoutManager(new LinearLayoutManager(this));
        adapter = new TransactionAdapter(this,transactionList);
        rvTransactions.setAdapter(adapter);
  
        // On click sign change
        tvSign.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                changeSign();
            }
        });
  
        // On click Send
        ivSend.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
  
                // Input Validation
                if(etAmount.getText().toString().trim().isEmpty())
                {
                    etAmount.setError("Enter Amount!");
                    return;
                }
                if(etMessage.getText().toString().isEmpty())
                {
                    etMessage.setError("Enter a message!");
                    return;
                }
                try {
                    int amt = Integer.parseInt(etAmount.getText().toString().trim());
                      
                      // Adding Transaction to recycler View
                    sendTransaction(amt,etMessage.getText().toString().trim(),positive);
                    checkIfEmpty(transactionList.size());
                      
                      // To update Balance
                    setBalance(transactionList);
                    etAmount.setText("");
                    etMessage.setText("");
                }
                catch (Exception e){
                    etAmount.setError("Amount should be integer greater than zero!");
                }
            }
        });
    }
  
    // To set custom action bar
    private void setCustomActionBar() {
        this.getSupportActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
        getSupportActionBar().setDisplayShowCustomEnabled(true);
        View v = LayoutInflater.from(this).inflate(R.layout.custom_action_bar,null);
          
          // TextView to show Balance
        tvBalance = v.findViewById(R.id.tvBalance);
         
          // Setting balance
        setBalance(transactionList);
        getSupportActionBar().setCustomView(v);
        getSupportActionBar().setElevation(0);
    }
  
    // To set Balance along with sign (spent(-) or received(+))
    public static void setBalance(ArrayList<TransactionClass> transactionList){
        int bal = calculateBalance(transactionList);
        if(bal<0)
        {
            tvBalance.setText("- ₹"+calculateBalance(transactionList)*-1);
        }
        else {
            tvBalance.setText("+ ₹"+calculateBalance(transactionList));
        }
    }
  
    // To load data from shared preference
    private void loadData() {
        SharedPreferences pref = getSharedPreferences("com.cs.ec",MODE_PRIVATE);
        Gson gson = new Gson();
        String json = pref.getString("transactions",null);
        Type type = new TypeToken<ArrayList<TransactionClass>>(){}.getType();
        if(json!=null)
        {
            transactionList=gson.fromJson(json,type);
        }
    }
  
    // To add transaction
    private void sendTransaction(int amt,String msg, boolean positive) {
        transactionList.add(new TransactionClass(amt,msg,positive));
        adapter.notifyDataSetChanged();
        rvTransactions.smoothScrollToPosition(transactionList.size()-1);
    }
  
    // Function to change sign
    private void changeSign() {
        if(positive)
        {
            tvSign.setText("-₹");
            tvSign.setTextColor(Color.parseColor("#F44336"));
            positive = false;
        }
        else {
            tvSign.setText("+₹");
            tvSign.setTextColor(Color.parseColor("#00c853"));
            positive = true;
        }
    }
  
    // To check if transaction list is empty
    public static void checkIfEmpty(int size) {
        if (size == 0)
        {
            MainActivity.tvEmpty.setVisibility(View.VISIBLE);
        }
        else {
            MainActivity.tvEmpty.setVisibility(View.GONE);
        }
    }
  
    // To Calculate Balance by iterating through all transactions
    public static int calculateBalance(ArrayList<TransactionClass> transactionList)
    {
        int bal = 0;
        for(TransactionClass transaction : transactionList)
        {
            if(transaction.isPositive())
            {
                bal+=transaction.getAmount();
            }
            else {
                bal-=transaction.getAmount();
            }
        }
        return bal;
    }
  
    // Initializing Views
    private void initViews() {
        transactionList = new ArrayList<TransactionClass>();
        tvSign = findViewById(R.id.tvSign);
        rvTransactions = findViewById(R.id.rvTransactions);
        etAmount = findViewById(R.id.etAmount);
        etMessage = findViewById(R.id.etMessage);
        ivSend = findViewById(R.id.ivSend);
        tvEmpty = findViewById(R.id.tvEmpty);
    }
  
    // Storing data locally 
      // using shared preferences
    // in onStop() method
    @Override
    protected void onStop() {
        super.onStop();
        SharedPreferences.Editor editor = getSharedPreferences("com.cs.ec",MODE_PRIVATE).edit();
        Gson gson = new Gson();
        String json = gson.toJson(transactionList);
        editor.putString("transactions",json);
        editor.apply();
    }
}


That’s it. Now we can run the application. Make sure that your project contains all the following files.

Here is the preview of the final application.

Output:



Last Updated : 29 Oct, 2021
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads