1

Building an Android QR Code Scanner with ML Kit: A Step-by-Step Guide

 1 year ago
source link: https://www.simplifiedcoding.net/android-qr-code-scanner-example/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Building an Android QR Code Scanner with ML Kit: A Step-by-Step Guide

April 17, 2023April 16, 2023 by Belal Khan

In this post, we will build an Android QR Code Scanner Example Project, with Firebase ML Kit.

This guide is part of my “Unlocking the Power of Machine Learning: ML-Kit’s Vision API on Android” course, which is absolutely free and you can check the full playlist below.

Introduction

For building this Android QR Code Scanner project, we will be using

CameraX

It is a Jetpack Library, that helps in developing camera apps. For our Android QR Code Scanner, we need Camera, and for the camera part of this project we will use CameraX.

ML-Kit’s Barcode Scanning API

It is Google’s Mobile SDK for machine learning. With ML-Kit we can use many Machine Learning APIs of Google. Barcode Scanning is one of the APIs of ML-Kit that we are going to use for building this project.

Project Setup

Ok, now after enough overview, let’s get started.

  • Create an Empty Project using the Empty Activity template in Android Studio.
  • Now open the app/build.gradle file and put the following dependencies in the dependencies block.
    implementation "com.google.mlkit:barcode-scanning:17.0.3"
    implementation "androidx.camera:camera-camera2:1.2.1"
    implementation "androidx.camera:camera-lifecycle:1.2.1"
    implementation "androidx.camera:camera-view:1.2.1"
  • We have added the required dependencies for this project.
  • Now enable viewBinding for the project.
    buildFeatures {
        viewBinding true

QR Code Scanner App Structure

This project contains two activities

  1. MainActivity: It is the entry point of our application. Here we have an area to display the QR Data and a Button to launch the QR Scanner.
  2. ScannerActivity: It is the main activity where the actual QR Scanner will happen. I have created a separate activity for the scanner because now you can reuse it anywhere you need to scan the QR Code. You can launch this activity and get the QR Data back. (Shortly you will understand how).

Now let’s start with the ScannerActivity.

Scanner Activity to Scan QR Code

Create a new Empty Activity in your project. It is our Scanner Activity, so we must show Camera Preview on this screen.

For displaying Camera Preview, we need PreviewView. So inside the layout file for this activity (activity_scanner.xml) put the following XML code.

<?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=".ScannerActivity">
    <androidx.camera.view.PreviewView
        android:id="@+id/preview_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

As you can see in the above design we just have a PreviewView, but if you need you can put more effort into designing an awesome-looking Scanner Screen.

I want to use this activity as a Stand Alone activity that will just Scan the QR and give the Scanned Data. And that is why I will define a companion object here, because I want to call a function to start this scanner.

    companion object {
        private val TAG = ScannerActivity::class.simpleName
        private var onScan: ((barcodes: List<Barcode>) -> Unit)? = null
        fun startScanner(context: Context, onScan: (barcodes: List<Barcode>) -> Unit) {
            this.onScan = onScan
            Intent(context, ScannerActivity::class.java).also {
                context.startActivity(it)

In the above code, you can see we have TAG (for logging purposes), and then we have onScan: ((barcodes: List<Barcode>) -> Unit)? = null. We can call this onScan()  to send the result back to calling place.

Finally, inside the function startScanner() I am assigning the onScan value and starting the ScannerActivity. This way whenever we need to start the scanner we can use the following code.

fun startScanner() {
    ScannerActivity.startScanner(this) { barcodes ->

Here inside the trailing lambda, we will get the scanned barcode.

Please note that, opening this ScannerActivity requires Camera Permission, and I am assuming here that the caller has got the camera permission already before starting this activity.

Now let’s make our QR Scanner.

Remember that in this project we are using viewBinding, which means first you need to change your Empty Activity as below.

class ScannerActivity : AppCompatActivity() {
    private lateinit var binding: ActivityScannerBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityScannerBinding.inflate(layoutInflater)
        setContentView(binding.root)

Now we need a few objects in our Scanner Activity.

    private lateinit var cameraSelector: CameraSelector
    private lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>
    private lateinit var processCameraProvider: ProcessCameraProvider
    private lateinit var cameraPreview: Preview
    private lateinit var imageAnalysis: ImageAnalysis

Let’s describe every object one by one.

  1. CameraSelector: It is needed because in almost all devices we have two cameras (Front Facing and Rear Facing). For this example, we will use the Rear Facing camera only.
  2. ListenableFuture<ProcessCameraProvider>: The class ProcessCameraProvider provides a listenable future that returns ProcessCameraProvider.
  3. ProcessCameraProvider: We need to ProcessCameraProvider instance to show the Camera Preview and Scan the QR. We will get its instance from the ListenableFuture<ProcessCameraProvider>  instance.
  4. Preview: It is required to display the Camera Preview.
  5. ImageAnalysis: All the above instances are related to Camera only, but ImageAnalysis is the main object which is needed to analyse the camera images to read QR Codes if any.

Now let’s get the ProcessCameraProvider inside the onCreate()  function.

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityScannerBinding.inflate(layoutInflater)
        setContentView(binding.root)
        cameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
        cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        cameraProviderFuture.addListener(
                    processCameraProvider = cameraProviderFuture.get()
                    bindCameraPreview()
                    bindInputAnalyser()
                } catch (e: ExecutionException) {
                    Log.e(TAG, "Unhandled exception", e)
                } catch (e: InterruptedException) {
                    Log.e(TAG, "Unhandled exception", e)
            }, ContextCompat.getMainExecutor(this)

In the above code, you can see we got the processCameraProvider from the cameraProviderFuture ( processCameraProvider = cameraProviderFuture.get()).

And after getting the processCameraProvider, we are calling two functions.

  1. bindCameraPreview(): This function will bind the camera preview to the PreviewView that we created in the layout file.
  2. bindInputAnalyser: This function will bind the Input Analyser, which will actually get the QR Data from the camera images if any.

Now let’s code the bindCameraPreview() .

private fun bindCameraPreview() {
    cameraPreview = Preview.Builder()
        .setTargetRotation(binding.previewView.display.rotation)
        .build()
    cameraPreview.setSurfaceProvider(binding.previewView.surfaceProvider)
        processCameraProvider.bindToLifecycle(this, cameraSelector, cameraPreview)
    } catch (illegalStateException: IllegalStateException) {
        Log.e(TAG, illegalStateException.message ?: "IllegalStateException")
    } catch (illegalArgumentException: IllegalArgumentException) {
        Log.e(TAG, illegalArgumentException.message ?: "IllegalArgumentException")

In the above function, we performed the following operation.

  1. Initialized cameraPreview using Preview.Builder() .
  2. We set the PreviewSurface.
  3. We bound the processCameraProvider to LifeCycle and here we are passing the current activity, cameraSelection and the cameraPreview.

We have the Camera Preview ready.

Now let’s complete the bindInputAnalyser().

private fun bindInputAnalyser() {
    val barcodeScanner: BarcodeScanner = BarcodeScanning.getClient(
        BarcodeScannerOptions.Builder()
            .setBarcodeFormats(Barcode.FORMAT_QR_CODE)
            .build()
    imageAnalysis = ImageAnalysis.Builder()
        .setTargetRotation(binding.previewView.display.rotation)
        .build()
    val cameraExecutor = Executors.newSingleThreadExecutor()
    imageAnalysis.setAnalyzer(cameraExecutor) { imageProxy ->
        processImageProxy(barcodeScanner, imageProxy)
        processCameraProvider.bindToLifecycle(this, cameraSelector, imageAnalysis)
    } catch (illegalStateException: IllegalStateException) {
        Log.e(TAG, illegalStateException.message ?: "IllegalStateException")
    } catch (illegalArgumentException: IllegalArgumentException) {
        Log.e(TAG, illegalArgumentException.message ?: "IllegalArgumentException")

Here we did the following things.

  1. Got the BarCodeScanning Client, here the getClient()  function takes BarcodeScannerOptions and for the option, we are setting the Barcode Format as Barcode.FORMAT_QR_CODE  as we want to scan QR Codes.
  2. Built the imageAnalysis object using ImageAnalysis.Builder()  it is needed to analyse the camera images for QR Code.
  3. Called setAnalyzer()  function from imageAnalysis, and here inside the trailing lambda, we get the imageProxy that we will process and find the QR Code if any. (We need to define the processImageProxy()  function)
  4. Finally, we are again binding the processCameraProvider but this time the last parameter is imageAnalysis to analyze camera images for QR Code.

Now let’s define the processImageProxy()  function.

    private fun processImageProxy(
        barcodeScanner: BarcodeScanner,
        imageProxy: ImageProxy
        val inputImage =
            InputImage.fromMediaImage(imageProxy.image!!, imageProxy.imageInfo.rotationDegrees)
        barcodeScanner.process(inputImage)
            .addOnSuccessListener { barcodes ->
                if (barcodes.isNotEmpty()) {
                    onScan?.invoke(barcodes)
                    onScan = null
                    finish()
            .addOnFailureListener {
                Log.e(TAG, it.message ?: it.toString())
            }.addOnCompleteListener {
                imageProxy.close()

In the above function, we did the following thing.

  1. First, we got the inputImage from ImageProxy.
  2. Then with the help of barcodeScanner we processed the inputImage.
  3. Inside the success callback, if we have one or more barcodes, we are making the call to onScan which will send the result to the caller. And after this, we are also finishing the current activity.
  4. Inside the complete listener, we are closing the imageProxy.
  5. Inside the failure listener, we are logging the failure.

Now let’s complete the MainActivity that will launch our Scanner.

Launching QR Scanner

The entry point to our application is MainActivity. This activity is responsible to launch the scanner, and for this, we need to perform the following things.

  1. Ask Camera Permissions
  2. If Camera Permission is Granted, launch the Scanner Activity.
  3. Show the QR Data that is scanned using Scanner Activity.

Let’s first start with designing the UI.

Put the following XML code in your activity_main.xml file.

<?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=".MainActivity">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="450dp"
        android:layout_marginStart="32dp"
        android:layout_marginTop="32dp"
        android:layout_marginEnd="32dp"
        android:layout_marginBottom="32dp"
        android:background="#DADADA"
        android:orientation="vertical"
        android:padding="12dp"
        app:layout_constraintBottom_toTopOf="@+id/button_open_scanner"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">
        <TextView
            style="@style/TextAppearance.AppCompat.Subhead"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="QR Type" />
        <TextView
            android:id="@+id/text_view_qr_type"
            style="@style/TextAppearance.AppCompat.Title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="URL" />
        <TextView
            style="@style/TextAppearance.AppCompat.Subhead"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="24dp"
            android:text="QR Content" />
        <TextView
            android:id="@+id/text_view_qr_content"
            style="@style/TextAppearance.AppCompat.Title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="content" />
    </LinearLayout>
    <Button
        android:id="@+id/button_open_scanner"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="64dp"
        android:text="Open Scanner"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

With the above XML code you will see the following UI.

android qr code scanner example

Android QR Code Scanner

In this activity, we will display the Type and Content of the QR Code. And it is actually very easy because all the work is already done in the Scanner Activity. We just need to define the following function.

    private fun startScanner() {
        ScannerActivity.startScanner(this) { barcodes ->
            barcodes.forEach { barcode ->
                when (barcode.valueType) {
                    Barcode.TYPE_URL -> {
                        binding.textViewQrType.text = "URL"
                        binding.textViewQrContent.text = barcode.url.toString()
                    Barcode.TYPE_CONTACT_INFO -> {
                        binding.textViewQrType.text = "Contact"
                        binding.textViewQrContent.text = barcode.contactInfo.toString()
                    else -> {
                        binding.textViewQrType.text = "Other"
                        binding.textViewQrContent.text = barcode.rawValue.toString()

As you can see in the above code snippet, we just started the scanner and in the trailing lambda, we are displaying the barcode content to the UI. Simple and Straightforward. We can call this function on the button click, but an important thing needed is the Camera Permission and we need to handle asking for Camera Permission in this activity.

I won’t discuss runtime permissions in this post, as it is a separate topic that we will cover in another post. But for now, here is my MainActivity.

class MainActivity : AppCompatActivity() {
    private val cameraPermission = android.Manifest.permission.CAMERA
    private lateinit var binding: ActivityMainBinding
    private val requestPermissionLauncher = registerForActivityResult(RequestPermission()) { isGranted ->
        if (isGranted) {
            startScanner()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.buttonOpenScanner.setOnClickListener {
            requestCameraAndStartScanner()
    private fun requestCameraAndStartScanner() {
        if (isPermissionGranted(cameraPermission)) {
            startScanner()
        } else {
            requestCameraPermission()
    private fun requestCameraPermission() {
        when {
            shouldShowRequestPermissionRationale(cameraPermission) -> {
                cameraPermissionRequest(
                    positive = { openPermissionSetting() }
            else -> {
                requestPermissionLauncher.launch(cameraPermission)
    private fun startScanner() {
        ScannerActivity.startScanner(this) { barcodes ->
            barcodes.forEach { barcode ->
                when (barcode.valueType) {
                    Barcode.TYPE_URL -> {
                        binding.textViewQrType.text = "URL"
                        binding.textViewQrContent.text = barcode.url.toString()
                    Barcode.TYPE_CONTACT_INFO -> {
                        binding.textViewQrType.text = "Contact"
                        binding.textViewQrContent.text = barcode.contactInfo.toString()
                    else -> {
                        binding.textViewQrType.text = "Other"
                        binding.textViewQrContent.text = barcode.rawValue.toString()

Make sure you add Camera Permission in the Manifest File as well.

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

Now, try the application and scan some QR Codes.

Android QR Code Scanner Source Code

In case you need my source code then you can get it from the following GitHub Repository.

Android QR Code Scanner Source Code

So that is all for this post friends, make sure you tell your friends about this tutorial. Thank You

Hi, my name is Belal Khan and I am a Google Developers Expert (GDE) for Android. The passion of teaching made me create this blog. If you are an Android Developer, or you are learning about Android Development, then I can help you a lot with Simplified Coding.

Related

Checkout these tutorials as well:

  • Circular Progress Bar Android Tutorial [4 Easy Steps]
  • Abstract RecyclerView Adapter to Eliminate Some Boiler-plate Code
  • Razorpay Integration in the Flutter App - A Complete Guide
  • Android Unit Test Tutorial - Writing Your First Unit Test
  • Android Save Bitmap to Gallery - Download and Save Image from URL
  • Android Hilt Tutorial - Injecting Dependencies with Hilt

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK