ZOFTINO.COM android and web dev tutorials

Android Barcode Scanning Example

This post explains how to develop barcode scanning feature in android app using Firebase machine learning kit which provides barcode scanning API.

Firebase ML Kit Barcode Scanning API

You can check the Firebase ML Kit barcodes documentation to know about features of it and the formats of barcodes it supports.

Let’s see how to use barcode scanning API to get barcode value. First create instance of FirebaseVisionBarcodeDetector object using FirebaseVision object.

        FirebaseVisionBarcodeDetector detector = FirebaseVision.getInstance()
                .getVisionBarcodeDetector();

Then call detectInImage() method on FirebaseVisionBarcodeDetector object by passing an argument of Bitmap of the image which contains barcode. You need to make sure that the Bitmap passed to detectInImage() should represent image which is upright. See the code in following sections to know how upright image is obtained in the example app.

Task<List<FirebaseVisionBarcode>> result = detector.detectInImage(image);

Then add listener to the resulting Task to capture barcode values. To onSuccess method of the listener, a list of FirebaseVisionBarcode objects is passed. From that, you can get raw barcode values by calling getRawValue() on FirebaseVisionBarcode objects.

    result.addOnSuccessListener(new OnSuccessListener<List<FirebaseVisionBarcode>>() {
        @Override
        public void onSuccess(List<FirebaseVisionBarcode> barcodes) {
            for (FirebaseVisionBarcode barcode : barcodes) {
                String rawValue = barcode.getRawValue();
            }
        }
    })
            .addOnFailureListener(new OnFailureListener() {
        @Override
        public void onFailure(@NonNull Exception e) {
            Log.e("read barcode", e.toString());
        }
    });

Barcode Scanning Example

The example app allow user to capture bar code picture of any product, to enter product details such as name and price and save the data. User can retrieve product details from db by scanning barcode of products.

Barcode is captured using camera app. Barcode value is extracted using Firebase ML kit barcode scanning API. Product data is stored in Firebase Firestore database.

Barcode scanning app main screen.

android barcode scanning example

Add product screen.

android barcode scanning firebase ml kit example add product details

Get product screen.

android barcode scanning firebase ml kit example get product details

Project Setup

To make your project ready for using Firebase ML kit, you need to follow the instructions listed in image text extraction example post.

In addition to Firebase ML kit dependencies, we need to add firebase core, firestore and material design dependencies to app build file, gradle.build.

implementation 'com.google.firebase:firebase-core:17.0.0'
implementation 'com.google.firebase:firebase-ml-vision:21.0.0'
implementation 'com.google.firebase:firebase-firestore:20.2.0'
implementation 'com.google.android.material:material:1.0.0-alpha1'

Capturing Barcode Picture

Camera app is used to capture barcode picture. So mention the use of camera feature in manifest xml file.

<uses-feature android:name="android.hardware.camera"
                  android:required="true" />

We need to add read and write external storage permissions.

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

To capture picture from app, call startActivityForResult() passing the intent which contains action and request information for camera app.

Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
cameraIntent.putExtra(MediaStore.EXTRA_FINISH_ON_COMPLETION, true);
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(cameraIntent, REQUEST_BARCODE);

Once picture of product barcode is taken, onActivityResult() method gets called. In that method, barcode picture is passed to Firebase ML kit barcode api to get barcode value.

Main Activity

public class MainActivity extends AppCompatActivity {

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

    public void addProduct(View v){
        Intent intent = new Intent(this, ProductAdditionActivity.class);
        startActivity(intent);
    }

    public void getProduct(View v){
        Intent intent = new Intent(this, ProductReaderActivity.class);
        startActivity(intent);
    }
}

Main Activity Layout

<?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:layout_margin="8dp"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/add_prd"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="Scan Barcode Add Product"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:onClick="addProduct"
        style="@style/Widget.AppCompat.Button.Colored"/>

    <Button
        android:id="@+id/get_prd"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="Scan Barcode Get Product"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/add_prd"
        android:onClick="getProduct"
        style="@style/Widget.AppCompat.Button.Colored"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Product Base Activity

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.FileProvider;

import com.google.firebase.firestore.FirebaseFirestore;

import java.io.File;
import java.io.IOException;

public class ProductBaseActivity extends AppCompatActivity {

    protected static String BARCODE_IMAGE = "barcodeImage";
    protected static final int REQUEST_BARCODE = 1;
    protected String barcodeFilePath;

    protected ImageView barcodeImage;
    protected TextView barcodeValue;


    protected FirebaseFirestore firestoreDB;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        barcodeImage = findViewById(R.id.barcode_img);
        barcodeValue = findViewById(R.id.barcode_value);

        firestoreDB = FirebaseFirestore.getInstance();
    }

    public void captureBarcodePic(View v) {
        Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        cameraIntent.putExtra(MediaStore.EXTRA_FINISH_ON_COMPLETION, true);
        if (cameraIntent.resolveActivity(getPackageManager()) != null) {
            startActivityForResult(cameraIntent, REQUEST_BARCODE);

            File barcodeFile = null;
            try {
                barcodeFile = getBarcodeImageFileHolder();
            } catch (IOException ex) {
                Toast.makeText(this, "Please try again.",
                        Toast.LENGTH_SHORT).show();
                return;
            }

            Uri photoURI = FileProvider.getUriForFile(this,
                    "com.zoftino.barcodescanner.fileprovider", barcodeFile);
            cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
            startActivityForResult(cameraIntent, REQUEST_BARCODE);
        }
    }

    protected File getBarcodeImageFileHolder() throws IOException {
        File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
        File image = File.createTempFile(BARCODE_IMAGE, ".jpg", storageDir);
        barcodeFilePath = image.getAbsolutePath();
        return image;
    }
}

Add Product Activity

import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

import androidx.annotation.NonNull;

import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.firestore.DocumentReference;

import java.io.File;

public class ProductAdditionActivity extends ProductBaseActivity {

    private EditText prdName;
    private EditText prdPrice;

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


        prdName = findViewById(R.id.product_name);
        prdPrice = findViewById(R.id.product_price);
    }

    public void saveProduct(View v) {
        addProductToDb(createProductObj());
    }

    private Product createProductObj(){
        final Product product = new Product();
        product.setProdId((String)barcodeValue.getText());
        product.setProdName(prdName.getText().toString());

        float price = Float.valueOf(prdPrice.getText().toString());
        product.setPrice(price);

        return product;
    }

    private void addProductToDb(Product product){
        firestoreDB.collection("products")
                .add(product)
                .addOnSuccessListener(new OnSuccessListener<DocumentReference>() {
                    @Override
                    public void onSuccess(DocumentReference documentReference) {
                        refreshUi();
                        Toast.makeText(ProductAdditionActivity.this,
                                "Product has been added to db",
                                Toast.LENGTH_SHORT).show();
                    }
                })
                .addOnFailureListener(new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception e) {
                        Toast.makeText(ProductAdditionActivity.this,
                                "Product could not be added to db, try again",
                                Toast.LENGTH_SHORT).show();
                    }
                });
    }

    private void refreshUi(){
        prdPrice.setText("");
        prdName.setText("");
        barcodeImage.setImageBitmap(null);
        barcodeValue.setText("");
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_BARCODE && resultCode == RESULT_OK) {
            File imgFile = new  File(barcodeFilePath);
            if(imgFile.exists())            {
                barcodeImage.setImageURI(Uri.fromFile(imgFile));
                Bitmap bitmap = ProductUtil.getUprightImage(barcodeFilePath);
                ProductUtil.setBarcodeValue(bitmap, barcodeValue);
            }
        }
    }
}

Add Product Activity Layout

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="8dp">

    <Button
        android:id="@+id/capture_barcode"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="First Capture Barcode Picture"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:onClick="captureBarcodePic"
        style="@style/Widget.AppCompat.Button.Colored"/>
    <ImageView
        android:id="@+id/barcode_img"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_marginTop="10dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/capture_barcode"/>

    <TextView
        android:id="@+id/barcode_value_l"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:textAppearance="@style/TextAppearance.AppCompat.Headline"
        android:text="Product Id from Barcode"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/barcode_img"/>
    <TextView
        android:id="@+id/barcode_value"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/barcode_value_l"/>

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/product_name_l"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/barcode_value"
        android:hint="Enter Product name">
        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/product_name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </com.google.android.material.textfield.TextInputLayout>
    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/product_price_l"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/product_name_l"
        android:hint="Enter Product price">
        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/product_price"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="number"/>
    </com.google.android.material.textfield.TextInputLayout>
    <Button
        android:id="@+id/save_prd"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="Save Product"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/product_price_l"
        android:onClick="saveProduct"
        style="@style/Widget.AppCompat.Button.Colored"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Get Product Activity

import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;

import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.firestore.QueryDocumentSnapshot;
import com.google.firebase.firestore.QuerySnapshot;
import com.google.firebase.ml.vision.barcode.FirebaseVisionBarcode;

import java.io.File;
import java.util.List;

public class ProductReaderActivity extends ProductBaseActivity {

    private TextView prdName;
    private TextView prdPrice;

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

        prdName = findViewById(R.id.product_name);
        prdPrice = findViewById(R.id.product_price);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_BARCODE && resultCode == RESULT_OK) {
            File imgFile = new File(barcodeFilePath);
            if (imgFile.exists()) {
                Bitmap bitmap = ProductUtil.getUprightImage(barcodeFilePath);
                Task<List<FirebaseVisionBarcode>> result =
                        ProductUtil.readBarcodeValueTask(bitmap);
                readProductFromDb(result);
            }
        }
    }

    private void getProduct(String prodId) {
        firestoreDB.collection("products")
                .whereEqualTo("prodId", prodId)
                .get()
                .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                    @Override
                    public void onComplete(@NonNull Task<QuerySnapshot> task) {
                        if (task.isSuccessful()) {
                            for (QueryDocumentSnapshot document : task.getResult()) {
                                displayProductDetails(document.toObject(Product.class));
                                break;
                            }
                        } else {
                            Toast.makeText(ProductReaderActivity.this,
                                    "Failed to get product data, please try again.",
                                    Toast.LENGTH_SHORT).show();
                            Log.d("get product",
                                    "Error getting documents: ", task.getException());
                        }
                    }
                });

    }

    private void displayProductDetails(Product product) {
        prdName.setText(product.getProdName());
        prdPrice.setText(String.valueOf(product.getPrice()));
        barcodeValue.setText(product.getProdId());
    }

    private void readProductFromDb(Task<List<FirebaseVisionBarcode>> result) {
        result.addOnSuccessListener(new OnSuccessListener<List<FirebaseVisionBarcode>>() {
            @Override
            public void onSuccess(List<FirebaseVisionBarcode> barcodes) {
                if(barcodes.size() == 0){
                    Toast.makeText(ProductReaderActivity.this,
                            "No barcodes found, please try again.",
                            Toast.LENGTH_SHORT).show();
                }
                for (FirebaseVisionBarcode barcode : barcodes) {
                    String rawValue = barcode.getRawValue();
                    getProduct(rawValue);
                }
            }
        }).addOnFailureListener(new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception e) {
                        Toast.makeText(ProductReaderActivity.this,
                                "Can not read barcode, please try again.",
                                Toast.LENGTH_SHORT).show();
                        Log.d("get product",
                                "Error reading barcode: "+ e.toString());
                    }
        });
    }
}

Get Product Activity Layout

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="8dp">
    <Button
        android:id="@+id/capture_barcode"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="Take Barcode Picture"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:onClick="captureBarcodePic"
        style="@style/Widget.AppCompat.Button.Colored"/>
    <TextView
        android:id="@+id/barcode_value_l"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="Product Id from Barcode"
        android:textAppearance="@style/TextAppearance.AppCompat.Headline"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/capture_barcode"/>
    <TextView
        android:id="@+id/barcode_value"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/barcode_value_l"/>
    <TextView
        android:id="@+id/product_name_l"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="Product Name"
        android:textAppearance="@style/TextAppearance.AppCompat.Headline"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@id/product_name"
        app:layout_constraintTop_toBottomOf="@+id/barcode_value"/>
    <TextView
        android:id="@+id/product_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        app:layout_constraintLeft_toRightOf="@id/product_name_l"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/barcode_value"/>
    <TextView
        android:id="@+id/product_price_l"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="Product Price"
        android:textAppearance="@style/TextAppearance.AppCompat.Headline"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@id/product_price"
        app:layout_constraintTop_toBottomOf="@+id/product_name"/>
    <TextView
        android:id="@+id/product_price"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        app:layout_constraintLeft_toRightOf="@id/product_price_l"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/product_name"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Product Utility

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ExifInterface;
import android.util.Log;
import android.widget.TextView;

import androidx.annotation.NonNull;

import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.ml.vision.FirebaseVision;
import com.google.firebase.ml.vision.barcode.FirebaseVisionBarcode;
import com.google.firebase.ml.vision.barcode.FirebaseVisionBarcodeDetector;
import com.google.firebase.ml.vision.common.FirebaseVisionImage;

import java.io.IOException;
import java.util.List;

public class ProductUtil {

    public static Task<List<FirebaseVisionBarcode>>
    readBarcodeValueTask(Bitmap bitmap) {
        FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);
        FirebaseVisionBarcodeDetector detector = FirebaseVision.getInstance()
                .getVisionBarcodeDetector();

        Task<List<FirebaseVisionBarcode>> result = detector.detectInImage(image);
        return result;
    }

    public static void setBarcodeValue(Bitmap bitmap, final TextView textView) {
        readBarcodeValueTask(bitmap)
                .addOnSuccessListener(new OnSuccessListener<List<FirebaseVisionBarcode>>() {
                    @Override
                    public void onSuccess(List<FirebaseVisionBarcode> barcodes) {
                        for (FirebaseVisionBarcode barcode : barcodes) {
                            String rawValue = barcode.getRawValue();
                            textView.setText(rawValue);
                        }
                    }
                })
                .addOnFailureListener(new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception e) {
                        textView.setText("could not read barcode from image");
                        Log.e("Prod Util read barcode", e.toString());
                    }
                });

    }

    public static Bitmap getUprightImage(String imgUrl) {

        ExifInterface exif = null;
        try {
            exif = new ExifInterface(imgUrl);
        } catch (IOException e) {
        }

        int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);
        int rotation = 0;
        switch (orientation) {
            case 3:
                rotation = 180;
                break;
            case 6:
                rotation = 90;
                break;
            case 8:
                rotation = 270;
                break;
        }
        Matrix matrix = new Matrix();
        matrix.postRotate(rotation);

        Bitmap bitmap = BitmapFactory.decodeFile(imgUrl);
        //rotate image
        bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
                bitmap.getHeight(), matrix, true);
        return bitmap;
    }
}