How to Build a Barcode Scanner Invoice Generator App in Android Java
Introduction
In this tutorial, we will explore how to develop a Barcode Scanner Invoice Generator App in Android Java that allows users to scan barcodes, add products to a list, and generate a PDF invoice. This app is ideal for small businesses or retail stores that need a quick and efficient way to manage transactions. By the end of this guide, you’ll be able to build a fully functional barcode scanner invoice generator app using modern Android development tools.
Prerequisites
Before getting started, ensure you have the following:
- Android Studio installed
- Basic knowledge of Java/Kotlin
- Dependencies for barcode scanning and PDF generation
Step 1: Adding Dependencies
To enable barcode scanning and PDF generation, add the following dependencies to your build.gradle
file:
implementation 'com.google.mlkit:barcode-scanning:17.2.0'
implementation 'com.itextpdf:itext7-core:7.1.15'
Step 2: Implement Barcode Scanner
To scan barcodes, use the Google ML Kit. Integrate the camera preview and process the scanned barcode:
BarcodeScannerOptions options =
new BarcodeScannerOptions.Builder()
.setBarcodeFormats(Barcode.FORMAT_ALL_FORMATS)
.build();
BarcodeScanner scanner = BarcodeScanning.getClient(options);
Once a barcode is scanned, retrieve the product details and add them to a list.
Step 3: Displaying Items in RecyclerView
Create a RecyclerView
adapter to show scanned products dynamically:
public class ProductAdapter extends RecyclerView.Adapter<ProductAdapter.ViewHolder> {
private List<Product> productList;
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Product product = productList.get(position);
holder.name.setText(product.getName());
holder.price.setText(String.valueOf(product.getPrice()));
}
}
Step 4: Generating a PDF Invoice
Use iText7 to create a PDF file containing the product details and save it to device storage:
PdfWriter writer = new PdfWriter(filePath);
PdfDocument pdfDoc = new PdfDocument(writer);
Document document = new Document(pdfDoc);
document.add(new Paragraph("Invoice"));
for (Product product : productList) {
document.add(new Paragraph(product.getName() + " - " + product.getPrice()));
}
document.close();
Complete Code Here
1. MainActivity.java
public class MainActivity extends AppCompatActivity {
Button btnCreatePDf;
Bitmap bitmap, scaledBitmap;
EditText etCustomerName;
private final String[] informationArray = new String[]{"Name", "Company Name", "Address", "Phone", "Email"};
private int srNumber;
String ItemName, price, Quantity, priceTotal;
private RecyclerView recyclerView;
private MyAdapter adapter;
private final List listItem = new ArrayList<>();
private final List fullCodeList = new ArrayList<>();
private static final int CAMERA_PERMISSION_REQUEST_CODE = 200;
private DecoratedBarcodeView barcodeView;
private final Map<String, String> map = new HashMap<>();
int startingIndex = 5;
String copyDecodeText;
int yAxisForValue = 280;
private List totalPricePerItemList = new ArrayList<>();
private String customerName;
String pattern = "^[-+]?[\\d&]*\\.?\\d+$";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
etCustomerName = findViewById(R.id.etCustomerName);
btnCreatePDf = findViewById(R.id.btnNext);
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.logo_2);
recyclerView = findViewById(R.id.myRecyclerView);
barcodeView = findViewById(R.id.zxingBarcodeScanner);
barcodeView.setStatusText("");
recyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter = new MyAdapter(this, listItem, (position, quantity) -> {
listItem.get(position).setItemQuantity(quantity);
});
recyclerView.setAdapter(adapter);
map.put("1244", "Cold Drink");
map.put("0054", "Burger");
map.put("0187", "Sandwich");
map.put("7176", "Pizza");
startScanning();
//scaledBitmap = Bitmap.createScaledBitmap(bitmap,100,100,false);
ActivityCompat.requestPermissions(this, new String[]{
Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA}, PackageManager.PERMISSION_GRANTED);
{
// createPdf();
startScanning();
createPdf();
}
}
private void startScanning() {
barcodeView.decodeContinuous(new BarcodeCallback() {
@Override
public void barcodeResult(BarcodeResult result) {
// Handle the decoded result
String decodedText = result.getText();
// addItemToRecyclerView(decodedText);
if (decodedText.length() == 14) {
// Check if barcode has been scanned before
if (!fullCodeList.contains(decodedText)) {
fullCodeList.add(decodedText);
copyDecodeText = decodedText;
// Extract the last 4 digits using substring
double lastDigitPrice = Double.parseDouble(decodedText.substring(decodedText.length() - 4));
String productId = copyDecodeText.substring(startingIndex, startingIndex + 4);
if (String.valueOf(lastDigitPrice).matches(pattern) && productId.matches(pattern)) {
ItemModel itemModel = new ItemModel(lastDigitPrice, map.get(productId), 1);
addItemToRecyclerView(itemModel);
}
}
}
}
@Override
public void possibleResultPoints(List resultPoints) {
// You can use this callback method to show visual cues on the viewfinder.
}
});
}
private void addItemToRecyclerView(ItemModel item) {
// listItem.add(item);
// adapter.addItem(item);
listItem.add(item);
adapter.notifyItemInserted(listItem.size() - 1);
}
private void createPdf() {
btnCreatePDf.setOnClickListener(v -> {
if(etCustomerName.getText().length() != 0 && adapter.getItemCount() != 0){
customerName = etCustomerName.getText().toString();
PdfDocument pdfDocument = new PdfDocument();
Paint paint = new Paint();
// paint.setLetterSpacing(0.01f);
PdfDocument.PageInfo pageInfo1 = new PdfDocument.PageInfo.Builder(595, 842, 1).create();
PdfDocument.Page myPage1 = pdfDocument.startPage(pageInfo1);
Canvas canvas = myPage1.getCanvas();
// insert the picture
int endPosition = pageInfo1.getPageWidth() - 100;
scaledBitmap = Bitmap.createScaledBitmap(bitmap, 100, 100, false);
canvas.drawBitmap(scaledBitmap, endPosition, 0, paint);
// draw a text as like invoice
paint.setTextAlign(Paint.Align.CENTER);
paint.setTextSize(32.0f);
paint.setColor(ContextCompat.getColor(this, R.color.green));
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
canvas.drawText("Invoice", (float) pageInfo1.getPageWidth() / 2, 100, paint);
// customer name headiing
paint.setTextAlign(Paint.Align.LEFT);
paint.setTextSize(10.0f);
paint.setColor(Color.BLACK);
paint.setTypeface(Typeface.defaultFromStyle(Typeface.ITALIC));
canvas.drawText("Customer Name: "+customerName, 80, 170, paint);
/* int endX = pageInfo1.getPageWidth() - 5;
float textWidth = paint.measureText(getCurrentDate());
float x = pageInfo1.getPageWidth() - textWidth;*/
// date and time heading
paint.setTextAlign(Paint.Align.RIGHT);
canvas.drawText("Date: " + getCurrentDate(), 590, 170, paint);
canvas.drawText("Time: " + getCurrentTime(), 590, 180, paint);
//draw rectangle stroke etc
paint.setTextAlign(Paint.Align.RIGHT);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(1);
//draw rectangle
canvas.drawRect(10, 220, pageInfo1.getPageWidth() - 10, 250, paint);
// draw four line vertical, to make portions in rectangle
canvas.drawLine(100, 220, 100, 250, paint);
canvas.drawLine(300, 220, 300, 250, paint);
canvas.drawLine(430, 220, 430, 250, paint);
canvas.drawLine(500, 220, 500, 250, paint);
paint.setStrokeWidth(0);
paint.setStyle(Paint.Style.FILL);
paint.setTextSize(12.0f);
paint.setTextAlign(Paint.Align.LEFT);
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
// draw texts invoice data headings
canvas.drawText("Sr No.", 15, 240, paint);
canvas.drawText("Item Name", 105, 240, paint);
canvas.drawText("Price", 305, 240, paint);
canvas.drawText("Qty", 435, 240, paint);
canvas.drawText("Total", 505, 240, paint);
srNumber = 0;
for (int sNo = 0; sNo < listItem.size(); sNo++) { srNumber++; // draw values as like qty price item name etc canvas.drawText(srNumber + "", 17, yAxisForValue, paint); canvas.drawText(listItem.get(sNo).getItemName(), 106, yAxisForValue, paint); canvas.drawText(String.valueOf(listItem.get(sNo).getItemPrice()), 306, yAxisForValue, paint); canvas.drawText(String.valueOf(listItem.get(sNo).getItemQuantity()), 435, yAxisForValue, paint); canvas.drawText(calculateTotalPerItem(listItem.get(sNo).getItemPrice(), listItem.get(sNo).getItemQuantity()), 508, yAxisForValue, paint); yAxisForValue += 20; } yAxisForValue += 20; // draw line below of value and above of total canvas.drawLine(300, yAxisForValue, pageInfo1.getPageWidth() - 10, yAxisForValue, paint); yAxisForValue += 20; // draw texts as like total canvas.drawText("Sub Total", 306, yAxisForValue, paint); canvas.drawText(String.valueOf(calculateSubTotal()), 508, yAxisForValue, paint); canvas.drawText(":", 435, yAxisForValue, paint); yAxisForValue += 20; canvas.drawText("Tax (5%)", 306, yAxisForValue, paint); canvas.drawText(String.valueOf(calculateTax()), 508, yAxisForValue, paint); canvas.drawText(":", 435, yAxisForValue, paint); Paint paint2 = new Paint(); paint2.setStrokeWidth(1); // Set the stroke width to 5 (adjust as needed) paint2.setColor(ContextCompat.getColor(this, R.color.black)); // Set the stroke color to black paint2.setStyle(Paint.Style.STROKE); // Set the style to fill and stroke yAxisForValue += 35; // this value uses for top int bottom = yAxisForValue + 45; paint.setColor(ContextCompat.getColor(this, R.color.green)); canvas.drawRect(300, yAxisForValue, pageInfo1.getPageWidth() - 10, bottom, paint); canvas.drawRect(300, yAxisForValue, pageInfo1.getPageWidth() - 10, bottom, paint2); int yValueForGrandTotal = bottom - yAxisForValue; int calc = yValueForGrandTotal / 3; int finalCalc = calc * 2; yAxisForValue += finalCalc; paint.setColor(Color.WHITE); paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD)); paint.setTextSize(16.0f); canvas.drawText("Total", 320, yAxisForValue, paint); canvas.drawText(String.valueOf(calculateSubTotal() + calculateTax()), 515, yAxisForValue, paint); paint.setColor(ContextCompat.getColor(this, R.color.black)); paint.setTextAlign(Paint.Align.LEFT); paint.setTextSize(8.0f); canvas.drawText("Invoice Number: " + System.currentTimeMillis(), 17, pageInfo1.getPageHeight() - 20, paint); paint.setTextAlign(Paint.Align.RIGHT); canvas.drawText("Generate by Al Saeed", pageInfo1.getPageWidth() - 17, pageInfo1.getPageHeight() - 20, paint); pdfDocument.finishPage(myPage1); String folderName = "AlsaeedFolder"; // Define your custom folder name File customFolder = new File(Environment.getExternalStorageDirectory(), folderName); if (!customFolder.exists()) { customFolder.mkdirs(); // Create the folder if it doesn't exist } File file = new File(customFolder, "myPDF.pdf"); try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
pdfDocument.writeTo(Files.newOutputStream(file.toPath()));
} else {
pdfDocument.writeTo(new FileOutputStream(file));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
Toast.makeText(this, "File save in " + file.getAbsolutePath(), Toast.LENGTH_SHORT).show();
// pdfDocument.close();
// Open the PDF file using a PDF viewer app
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri pdfUri = FileProvider.getUriForFile(this, "alsaeeddev.com.fileProvider", file);
intent.setDataAndType(pdfUri, "application/pdf");
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // Grant read permissions
try {
startActivity(intent); // Launch the PDF viewer activity
} catch (ActivityNotFoundException e) {
// Handle the case where no PDF viewer app is available
Toast.makeText(getApplicationContext(), "No PDF viewer app found", Toast.LENGTH_SHORT).show();
}
Toast.makeText(this, "File saved in " + file.getAbsolutePath(), Toast.LENGTH_SHORT).show();
pdfDocument.close();
}else {
etCustomerName.setError("Enter Name");
}
});
}
private String getCurrentDate() {
// Get the current date
Calendar calendar = Calendar.getInstance();
int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH) + 1; // Month is zero-based, so add 1
int day = calendar.get(Calendar.DAY_OF_MONTH);
// Construct the date string
return year + "-" + month + "-" + day;
}
private String getCurrentTime() {
// Get the current time
Calendar calendar = Calendar.getInstance();
int hour = calendar.get(Calendar.HOUR_OF_DAY); // 24-hour format
int minute = calendar.get(Calendar.MINUTE);
int second = calendar.get(Calendar.SECOND);
// Construct the time string
return hour + ":" + minute + ":" + second;
}
private String calculateTotalPerItem(double price, int quantity) {
double total = price * quantity;
totalPricePerItemList.add(total);
return String.valueOf(total);
}
private double calculateSubTotal() {
double subTotal = 0;
for (int i = 0; i < totalPricePerItemList.size(); i++) {
subTotal += totalPricePerItemList.get(i);
}
return subTotal;
}
private double calculateTax() {
return calculateSubTotal() * 0.5;
}
@Override
protected void onResume() {
super.onResume();
barcodeView.resume();
}
@Override
protected void onPause() {
super.onPause();
yAxisForValue = 280;
totalPricePerItemList.clear();
barcodeView.pause();
}
@Override
protected void onStop() {
yAxisForValue = 280;
totalPricePerItemList.clear();
super.onStop();
}
@Override
protected void onDestroy() {
listItem.clear();
fullCodeList.clear();
super.onDestroy();
}
}
</pre
2. MyAdapter.java
public class MyAdapter extends RecyclerView.Adapter {
private final List itemList;
private final Context context;
private final QuantityChangeListener listener;
public MyAdapter(Context context, List data, QuantityChangeListener listener) {
this.itemList = data;
this.listener = listener;
this.context = context;
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.rv_item, parent, false);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
holder.bindData(position);
// holder.itemView.setOnClickListener(v -> Toast.makeText(context, itemList.get(position).getItemQuantity(), Toast.LENGTH_SHORT).show());
}
@Override
public int getItemCount() {
return itemList.size();
}
public void addItem(String item) {
/* mData.add(item);
notifyItemInserted(mData.size() - 1);*/
/* mData.add(0, item); // Insert item at index 0
notifyItemInserted(0);*/
}
public class MyViewHolder extends RecyclerView.ViewHolder {
TextView itemName, itemPrice;
EditText editText;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
itemName = itemView.findViewById(R.id.tvItemName);
itemPrice = itemView.findViewById(R.id.tvItemPrice);
editText = itemView.findViewById(R.id.etQuantity);
}
public void bindData(int position) {
ItemModel item = itemList.get(position);
itemName.setText(item.getItemName());
itemPrice.setText(String.valueOf(item.getItemPrice()));
editText.setText(String.valueOf(item.getItemQuantity()));
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if(!TextUtils.isEmpty(s.toString())){
int quantity = Integer.parseInt(s.toString());
if(quantity != 0) {
item.setItemQuantity(quantity);
// if(listener != null && position != RecyclerView.NO_POSITION) {
listener.onQuantityChanged(position, quantity);
}
// }
}
}
@Override
public void afterTextChanged(Editable s) {
}
});
}
/* public interface QuantityChangeListener {
void onQuantityChanged(int position, int quantity);
}
public void setListener(QuantityChangeListener listener){
listener = listener;
}*/
}
}
</pre
3. QuantityChangeListener.java
public interface QuantityChangeListener {
void onQuantityChanged(int position, int quantity);
}
</pre
4. ItemModel.java
public class ItemModel {
private final double itemPrice;
private final String itemName;
private int itemQuantity;
public int getItemQuantity() {
return itemQuantity;
}
public void setItemQuantity(int quantity){
this.itemQuantity = quantity;
}
public double getItemPrice() {
return itemPrice;
}
public String getItemName() {
return itemName;
}
public ItemModel(double itemPrice, String itemName, int itemQuantity) {
this.itemPrice = itemPrice;
this.itemName = itemName;
this.itemQuantity = itemQuantity;
}
}
</pre
5. activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:elevation="10dp"
android:id="@+id/main"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/myRecyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="1dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="1dp"
app:layout_constraintBottom_toTopOf="@id/btnNext"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginBottom="8dp"
app:layout_constraintTop_toBottomOf="@+id/zxingBarcodeScanner" />
<com.journeyapps.barcodescanner.DecoratedBarcodeView
android:id="@+id/zxingBarcodeScanner"
android:layout_width="0dp"
android:layout_height="150dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/etCustomerName" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnNext"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:text="Generate Invoice"
android:textAllCaps="false"
android:background="@drawable/btn_bg"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginBottom="8dp"
app:layout_constraintStart_toStartOf="parent" />
<EditText
android:id="@+id/etCustomerName"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:background="@drawable/btn_bg"
android:ems="10"
android:textSize="14sp"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:inputType="text"
android:hint="Customer name"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
6. rv_item.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="5dp"
android:elevation="8dp"
android:backgroundTint="#E7EFE6"
app:cardCornerRadius="8dp"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tvItemName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:padding="3dp"
android:text="Pepsi"
android:textColor="@color/black"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@+id/etQuantity"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvItemPrice"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:padding="3dp"
android:text="$8"
android:layout_marginTop="3dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvItemName" />
<TextView
android:id="@+id/textView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:layout_marginBottom="8dp"
android:padding="3dp"
android:text="Price"
android:layout_marginTop="3dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/tvItemPrice"
app:layout_constraintTop_toBottomOf="@+id/tvItemName" />
<EditText
android:id="@+id/etQuantity"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:text="1"
android:layout_marginEnd="8dp"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:ems="4"
android:gravity="center"
app:layout_constraintBottom_toBottomOf="@+id/tvItemName"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/tvItemName" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
Output
Conclusion
By following these steps, you can build a functional Android application that scans barcodes, lists products in a RecyclerView
, and generates a PDF invoice. This project is highly useful for businesses looking for an easy invoicing system within their Android app.
📥
Click here to Download
the source code or ask questions in the comments below!