The Fastest Way to Build an Android QR Code Scanner using Java
Tags: java, android, programming, tutorial
Published: 2023-06-28 04:33:53

QR codes are becoming increasingly popular because they facilitate fast data retrieval, smooth financial transactions, and exciting new possibilities for personalization. Using a QR code scanner helps quickly access URLs, product information, event details, and special offers. 

The two main features of any reliable QR code scanner app are the camera preview and the scanning of QR codes. While dozens of QR code scanner applications are available on the Google Play Store, making your own QR code scanner is a much better option. What if we told you it could be done with just a few easy steps? In this blog, we will be sharing the quickest way to build an Android QR code scanner. You will discover how to implement camera preview and [QR code scanning SDK](https://www.dynamsoft.com/barcode-reader/barcode-types/qr-code/) in a step-by-step manner. 

**Prerequisites**

To implement the QR code scanner on Android, the following libraries are required. You are free to substitute your own libraries in their place.

Camera Preview SDK
CameraX

Since the Android Camera2 API is exceptionally challenging for beginners, Google released CameraX to streamline the development of camera applications. The codelab tutorial is an excellent introduction to CameraX.

**Installation**

You'll have to add the camera permission in AndroidManifest.xml: 

```
  <uses-feature android:name="android.hardware.camera.any" />
  <uses-permission android:name="android.permission.CAMERA" />
In app/build.gradle, add the dependency:
  dependencies {
      ...
      def camerax_version = "1.0.1"
      implementation "androidx.camera:camera-camera2:$camerax_version"
      implementation "androidx.camera:camera-lifecycle:$camerax_version"
      implementation "androidx.camera:camera-view:1.0.0-alpha27"
  }

```
**Dynamsoft Camera Enhancer v2.3**
Dynamsoft Camera Enhancer, like CameraX, is a wrapper for the Android Camera2 API. It has all the functionality of a regular camera, plus frame filtering to improve the quality of your shots. 

To evaluate CameraX, we also employ Dynamsoft Camera Enhancer.

**Installation**
Add the custom maven repository in settings.gradle, add the custom maven repository:

```
 dependencyResolutionManagement {
      repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
      repositories {
          ...
          maven{url "https://download2.dynamsoft.com/maven/dce/aar"}
      }
  }
Now, in app/build.gradle, add the dependency:
  dependencies {
      ...
      implementation 'com.dynamsoft:dynamsoftcameraenhancer:2.1.0@aar'
  }

```

## QR Code Scanning SDK

[Dynamsoft Barcode Reader v9.6](https://www.dynamsoft.com/barcode-reader/sdk-mobile/)
A barcode SDK that supports all standard linear barcode and 2D barcode formats.

**Installation**
Add the custom maven repository in settings.gradle, 

```
dependencyResolutionManagement {
      repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
      repositories {
          ...
          maven{url "https://download2.dynamsoft.com/maven/dbr/aar"}
      }
  }
In app/build.gradle, add the dependency:
dependencies {
      ...
      implementation 'com.dynamsoft:dynamsoftbarcodereader:9.0.0@aar'
  }

```
Additionally, a license key is required to activate the barcode SDK:

```
 BarcodeReader.initLicense(
      "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==",
          new DBRLicenseVerificationListener() {
              @Override
              public void DBRLicenseVerificationCallback(boolean isSuccessful, Exception e) {
              }
          });

```

## Develop Android Camera Preview within 5 Minutes!

### Implement Camera Preview with CameraX in Three Simple Steps
 

The default CameraX documentation is written in Kotlin. However, we will be using Java here. 

Build the user interface layout containing the CameraX preview view:

```
<?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"
     tools:context=".CameraXActivity">
    
     <androidx.camera.view.PreviewView
         android:id="@+id/camerax_viewFinder"
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
    
 </androidx.constraintlayout.widget.ConstraintLayout>

```
Now, let's check and request camera permissions:

```
 @Override
 protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.dce_main);
     previewView = findViewById(R.id.dce_viewFinder);
     cameraEnhancer = new CameraEnhancer(this);
     cameraEnhancer.setCameraView(previewView);
     cameraEnhancer.addListener(this);
 }
 @Override
 protected void onResume() {
     super.onResume();
     try {
         cameraEnhancer.open();
     } catch (CameraEnhancerException e) {
         e.printStackTrace();
     }
 }
 @Override
 protected void onPause() {
     super.onPause();
     try {
         cameraEnhancer.close();
     } catch (CameraEnhancerException e) {
         e.printStackTrace();
     }
 }

```

## All in One 

We'll construct an entrance activity for launching CameraX and Dynamsoft Camera Enhancer:

```
package com.example.qrcodescanner;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
public class EntryChoiceActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.entry_choice);
        findViewById(R.id.camerax_entry_point).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(EntryChoiceActivity.this, CameraXActivity.class);
                startActivity(intent);
            }
        });
        findViewById(R.id.dce_entry_point).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(EntryChoiceActivity.this, DceActivity.class);
                startActivity(intent);
            }
        });
    }
}

```

## How to Turn Android Camera into QR Code Scanner

To scan a QR code, we must continuously retrieve the camera's preview frames and send them to the QR code detector.

**Setting the Camera Frame Callback**

ImageAnalysis class can be used to receive the camera frames when using CameraX: 

```
ImageAnalysis analysisUseCase = new ImageAnalysis.Builder().build();
analysisUseCase.setAnalyzer(cameraExecutor,
        imageProxy -> {
            // image processing
            // Must call close to keep receiving frames.
            imageProxy.close();
        });
cameraProvider.bindToLifecycle(this, cameraSelector, previewUseCase, analysisUseCase);
In comparison, Dynamsoft Camera Enhancer is significantly simpler. The callback function resembles that of Android Camera1:
public class DceActivity extends AppCompatActivity implements DCEFrameListener {
    @Override
    public void frameOutputCallback(DCEFrame dceFrame, long l) {
        // image processing
    }
}

```
Different data types are returned by their response functions. For subsequent use, data type conversion is required.

## How to Decode QR Codes?
 

When using CameraX, ByteBuffer is firstly converted to byte[] and then the decodeBuffer() method is called: 

```
analysisUseCase.setAnalyzer(cameraExecutor,
imageProxy -> {
    TextResult[] results = null;
    ByteBuffer buffer = imageProxy.getPlanes()[0].getBuffer();
    int nRowStride = imageProxy.getPlanes()[0].getRowStride();
    int nPixelStride = imageProxy.getPlanes()[0].getPixelStride();
    int length = buffer.remaining();
    byte[] bytes = new byte[length];
    buffer.get(bytes);
    try {
        results = reader.decodeBuffer(bytes, imageProxy.getWidth(), imageProxy.getHeight(), nRowStride * nPixelStride, EnumImagePixelFormat.IPF_NV21, "");
    } catch (BarcodeReaderException e) {
        e.printStackTrace();
    }
    // Must call close to keep receiving frames.
    imageProxy.close();
});

When using Dynamsoft Camera Enhancer, we get Bitmap from the DCEFrame and then the decodeBufferedImage() method is called: 

public void frameOutputCallback(DCEFrame dceFrame, long l) {
    TextResult[] results = null;
    try {
        results = reader.decodeBufferedImage(dceFrame.toBitmap(), "");
    } catch (BarcodeReaderException e) {
        e.printStackTrace();
    }
}

```

## How to Use Zoom and Torch to Enhance the Frame Quality

The accuracy of barcode recognition is always dependent on the quality of the input image. The image can be enlarged by zooming the camera if the QR code is very small. If the input image is too poorly lit, we can activate the flashlight to enhance it. Both CameraX and Dynasoft Camera Enhancer have supported the camera control in full.

**Android Camera Zoom**

Using the finger pinch gesture, the zoom can be triggered. Hence, firstly we'll build the gesture detector and take over the onTouchEvent() method:

```
public class ZoomController {
    public final static String TAG = "ZoomController";
    private float currentFactor = 1.0f;
    private float minZoomRatio = 1.0f, maxZoomRatio = 1.0f;
    private ZoomStatus zoomStatus;
    private ScaleGestureDetector scaleGestureDetector;
    private ScaleGestureDetector.OnScaleGestureListener scaleGestureListener = new ScaleGestureDetector.OnScaleGestureListener() {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            Log.i(TAG, "onScale: " + detector.getScaleFactor());
            currentFactor = detector.getScaleFactor() * currentFactor;
            if (currentFactor < minZoomRatio) currentFactor = minZoomRatio;
            if (currentFactor > maxZoomRatio) currentFactor = maxZoomRatio;
            if (zoomStatus != null) {
                zoomStatus.onZoomChange(currentFactor);
            }
            return true;
        }
        @Override
        public boolean onScaleBegin(ScaleGestureDetector detector) {
            return true;
        }
        @Override
        public void onScaleEnd(ScaleGestureDetector detector) {
        }
    };
    public ZoomController(Activity activity) {
        scaleGestureDetector = new ScaleGestureDetector(activity, scaleGestureListener);
    }
    public interface ZoomStatus {
        void onZoomChange(float ratio);
    }
    public void addListener(ZoomStatus zoomStatus) {
        this.zoomStatus = zoomStatus;
    }
    public void initZoomRatio(float minZoomRatio, float maxZoomRatio) {
        this.minZoomRatio = minZoomRatio;
        this.maxZoomRatio = maxZoomRatio;
    }
    public boolean onTouchEvent(MotionEvent event) {
        return scaleGestureDetector.onTouchEvent(event);
    }
}
@Override
public boolean onTouchEvent(MotionEvent event) {
    zoomController.onTouchEvent(event);
    return super.onTouchEvent(event);
}

```
When the gesture is detected, we'll get the scale factor and use that as the zoom ratio.

**Setting camera zoom ratio with CameraX**

```
if (camera != null) {
    camera.getCameraControl().setZoomRatio(ratio);
}
```
**Setting camera zoom ratio with Dynamsoft Camera Enhancer**
```
try {
    cameraEnhancer.setZoom(ratio);
} catch (CameraEnhancerException e) {
    e.printStackTrace();
}

```
**Android Camera Torch**

To instantly turn on the flashlight, we keep an eye on the light value the light sensor gives us.

```
public class AutoTorchController implements SensorEventListener {
    public final static String TAG = "AutoTorchController";
    private SensorManager sensorManager;
    private TorchStatus torchStatus;
    public interface TorchStatus {
        void onTorchChange(boolean status);
    }
    public AutoTorchController(Activity activity) {
        sensorManager = (SensorManager)activity.getSystemService(SENSOR_SERVICE);
    }
    public void onStart() {
        Sensor lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
        if(lightSensor != null){
            sensorManager.registerListener(
                    this,
                    lightSensor,
                    SensorManager.SENSOR_DELAY_NORMAL);
        }
    }
    public void onStop() {
        sensorManager.unregisterListener(this);
    }
    @Override
    public void onSensorChanged(SensorEvent event) {
        if(event.sensor.getType() == Sensor.TYPE_LIGHT){
            if (event.values[0] < 20) {
                if (torchStatus != null) torchStatus.onTorchChange(true);
            }
            else {
                if (torchStatus != null) torchStatus.onTorchChange(false);
            }
        }
    }
    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
    }
    public void addListener(TorchStatus torchStatus) {
        this.torchStatus = torchStatus;
    }
}

```
**Toggling camera torch with CameraX**

```
if (camera != null) camera.getCameraControl().enableTorch(status);

```
**Toggling camera torch with Dynamsoft Camera Enhancer**

```
if (status) {
    try {
        cameraEnhancer.turnOnTorch();
    } catch (CameraEnhancerException e) {
        e.printStackTrace();
    }
}
else {
    try {
        cameraEnhancer.turnOffTorch();
    } catch (CameraEnhancerException e) {
        e.printStackTrace();
    }
}

```