How to create a QR code reader on Android with Android Studio

Video thumbnail

Continuing with image generation, as we saw before with Canvas in Android, let's see how we can generate a QR code in Android Studio with composable.

What is a QR code?

Before getting into the topic of how to develop a QR reader using a native library provided by Google, it is important to know what a QR code reader is and how its structure works, which is what we will explain next.

A QR code is nothing more than a system that allows information to be stored in a kind of next-generation barcode.

A QR code is a type of two-dimensional barcode in which information is encoded in a square like the following.

QR code
Example of a QR code.

QR codes are really useful for managing inventories, sharing data and information in an easy and very professional way; QR codes are currently a fundamental element in any small or large application due to their versatility when used.

What is a QR Code for? and how can we exploit them in our Android apps

As mentioned earlier, QR codes are widely used due to their versatility and great utility; they are used in all types of products from food to devices, cars, magazines and many other areas such as advertising, marketing, etc... and this is because with an Android app we can scan any type of QR code.

Although our case of interest is to show the process of creating an Android application to use it as a scanner in an Android app...

...Where we can process medicines and indicate to the client if the medicine is useful for them, having their profile previously loaded and without the need for the client to manually provide any information or data.

How to generate a QR code?

On the Internet you will find an immense number of ways to generate QR codes; we can even create QR codes with PHP, Java, Python libraries, etc.; and there are websites that are responsible for generating QR codes; on the blog, you can find several articles on how to generate a QR with different technologies.

Generating QR codes in Android with Jetpack Compose

We are going to learn how to generate QR codes from an Android application. This is particularly useful today, as almost any complex application requires mechanisms of this type: from cross-platform platforms like Netflix (to link devices) to payment gateways. QRs are the natural evolution of barcodes and are used a lot, so let's see how to implement them.

1. Dependency Configuration

For this we will use the ZXing (Zebra Crossing) library, which allows both scanning and generating codes.

app/build.gradle.kts

implementation("com.google.zxing:core:3.5.4")

As always, I already have part of the code ready. First, we add the package in the dependencies file. Always check the version; if you hover over it, the IDE will tell you the latest one. We click Sync Now and wait for the process to finish to avoid import errors.

Function Logic: From Data to Bitmap

We are going to create a function to generate the QR outside the main class, so that we can easily access it from our Composables. This function will receive the data (the text, URL or number we want to embed) and the image size.

The concept of BitMatrix and Bitmap

The function will generate a Bitmap. If you are not familiar with the term, a Bitmap is basically a matrix of bits that represents an image. It is a two-dimensional matrix (X and Y) where each position contains color information.

  1. Encoding: We use QRCodeWriter().encode() to convert the data into a BitMatrix.
  2. Dimensions: We get the width and height of the matrix (it is usually a square).
  3. Bitmap Creation: We configure the object with Bitmap.createBitmap. Although you will see old ways on the internet, the currently recommended one uses named parameters to define the color format (such as ARGB_8888).

Image Rendering in Kotlin

To generate the visual image, we iterate through the matrix pixel by pixel. Since a QR is traditionally black and white, we ask the matrix:

  • If there is data at position (X, Y), we paint the pixel black.
  • If not, we paint it white.
fun generarQR(datos: String, size: Int = 512): Bitmap? {
    return try {
        val writer = QRCodeWriter()
        // Creates the point matrix (BitMatrix)
        val bitMatrix = writer.encode(datos, BarcodeFormat.QR_CODE, size, size)
        val width = bitMatrix.width
        val height = bitMatrix.height
//        val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565)
        val bitmap = createBitmap(width, height, Bitmap.Config.RGB_565)
        // We iterate through the matrix to paint pixel by pixel
        for (x in 0 until width) {
            for (y in 0 until height) {
                 bitmap[x, y] = if (bitMatrix.get(x, y)) Color.BLACK else Color.WHITE
                // bitmap.setPixel(x, y, if (bitMatrix.get(x, y)) Color.BLACK else Color.WHITE)
            }
        }
        bitmap
    } catch (e: Exception) {
        e.printStackTrace()
        null
    }
}

Consumption in the Interface (Jetpack Compose)

To display the QR in our application, we will use a column and follow these steps:

  1. Use of remember: We call our generateQR function wrapping it in a remember. This is crucial so that the QR is not generated again in each UI recomposition, which would be very inefficient.
  2. Conversion to ImageBitmap: The Compose Image component expects an ImageBitmap. For this, we use the extension function .asImageBitmap() on our original Bitmap.
  3. User Interface: We add a Spacer, a descriptive text ("Scan this code") and finally display the image.
@Composable
fun PantallaQR(textoParaQR: String) {
    // We generate the bitmap and remember it
    val qrBitmap = remember(textoParaQR) {
        generarQR(textoParaQR)
    }
    Column(
        modifier = Modifier.fillMaxSize().padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        if (qrBitmap != null) {
            Image(
                bitmap = qrBitmap.asImageBitmap(),
                contentDescription = "QR Code of $textoParaQR",
                modifier = Modifier.size(250.dp)
            )
            Spacer(modifier = Modifier.height(16.dp))
            Text(text = "Scan to see content", style = MaterialTheme.typography.bodyMedium)
        } else {
            Text("Error generating QR")
        }
    }
}

When executed, we will have a QR code:

desarrollolibre qr
Example QR to test the scanner.

Create a QR code reader

To build a QR reader with Jetpack Compose, you would use a combination of modern libraries for camera and barcode scanning.

Advantages of Jetpack Compose for a QR Reader:

  • Declarative: Define the UI once, Compose handles the changes.
  • Less Code: Often requires less code to achieve the same result as with traditional XML and Java/Kotlin.
  • Reusability: Composables are easy to reuse and combine.
  • Kotlin Compatibility: Designed from scratch for Kotlin, taking advantage of its features.
  • Integration with Modern Libraries: Integrates perfectly with CameraX and ML Kit for an updated development experience.

1. Dependencies (build.gradle of the module):

// Jetpack Compose
implementation platform('androidx.compose:compose-bom:2024.04.00')
implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.ui:ui-graphics'
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.compose.material3:material3'
implementation 'androidx.activity:activity-compose:1.8.2' // Or the latest version
// CameraX
implementation 'androidx.camera:camera-core:1.3.2' // Or the latest version
implementation 'androidx.camera:camera-camera2:1.3.2'
implementation 'androidx.camera:camera-lifecycle:1.3.2'
implementation 'androidx.camera:camera-view:1.3.2' // For CameraPreview Composable
// ML Kit Barcode Scanning
implementation 'com.google.mlkit:barcode-scanning:17.2.0' // Or the latest version
// For models with Google Play Services (faster/more accurate recognition)
implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:18.3.0'

2. Permissions (AndroidManifest.xml):

As in the legacy approach, camera permission is required. Runtime permission handling would be done using libraries or directly with rememberPermissionState from Accompanist Permissions or a similar implementation.

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

3. Composable Structure (Pseudo-code):

Here we show you how a Composable would be structured to display the camera preview and integrate barcode scanning. The complex logic of the camera and image analysis would be abstracted into helper functions or classes.

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.common.InputImage
import java.util.concurrent.Executors
// Main Composable for the QR reader
@Composable
fun QRCodeScannerScreen() {
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current
    var hasCameraPermission by remember { mutableStateOf(false) }
    var qrCodeResult by remember { mutableStateOf("Scanning...") }
    // Launcher to request camera permissions
    val permissionLauncher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.RequestPermission(),
        onResult = { isGranted ->
            hasCameraPermission = isGranted
            if (!isGranted) {
                qrCodeResult = "Camera permission denied."
            }
        }
    )
    // Request permission when the Composable starts
    LaunchedEffect(Unit) {
        when {
            ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED -> {
                hasCameraPermission = true
            }
            else -> {
                permissionLauncher.launch(Manifest.permission.CAMERA)
            }
        }
    }
    Column(modifier = Modifier.fillMaxSize()) {
        if (hasCameraPermission) {
            CameraPreview(onQrCodeDetected = { result ->
                qrCodeResult = result
                // Here you could add logic to navigate, show a dialog, etc.
            })
            Spacer(Modifier.height(16.dp))
            Text(text = "Result: $qrCodeResult", modifier = Modifier.padding(16.dp))
        } else {
            // UI when there is no permission
            Text("We need camera permission to scan QR.", modifier = Modifier.padding(16.dp))
            Button(onClick = { permissionLauncher.launch(Manifest.permission.CAMERA) }) {
                Text("Grant Permission")
            }
        }
    }
}
@Composable
fun CameraPreview(onQrCodeDetected: (String) -> Unit) {
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current
    val cameraExecutor = remember { Executors.newSingleThreadExecutor() }
    AndroidView(
        modifier = Modifier
            .fillMaxWidth()
            .weight(1f),
        factory = { ctx ->
            val previewView = PreviewView(ctx)
            val cameraProviderFuture = ProcessCameraProvider.getInstance(ctx)
            cameraProviderFuture.addListener({
                val cameraProvider = cameraProviderFuture.get()
                val preview = Preview.Builder().build().also {
                    it.setSurfaceProvider(previewView.surfaceProvider)
                }
                val imageAnalyzer = ImageAnalysis.Builder()
                    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_LATEST)
                    .build()
                    .also {
                        it.setAnalyzer(cameraExecutor, BarcodeAnalyzer(onQrCodeDetected))
                    }
                val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
                try {
                    cameraProvider.unbindAll()
                    cameraProvider.bindToLifecycle(
                        lifecycleOwner,
                        cameraSelector,
                        preview,
                        imageAnalyzer
                    )
                } catch (exc: Exception) {
                    // Handle camera initialization errors
                }
            }, ContextCompat.getMainExecutor(ctx))
            previewView
        }
    )
    DisposableEffect(Unit) {
        onDispose {
            cameraExecutor.shutdown()
        }
    }
}
// Class to analyze camera frames
class BarcodeAnalyzer(private val onQrCodeDetected: (String) -> Unit) : ImageAnalysis.Analyzer {
    private val scanner = BarcodeScanning.getClient(
        BarcodeScannerOptions.Builder()
            .setBarcodeFormats(Barcode.FORMAT_QR_CODE)
            .build()
    )
    override fun analyze(imageProxy: ImageProxy) {
        val mediaImage = imageProxy.image
        if (mediaImage != null) {
            val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
            scanner.process(image)
                .addOnSuccessListener { barcodes ->
                    if (barcodes.isNotEmpty()) {
                        barcodes.firstOrNull()?.rawValue?.let { rawValue ->
                            onQrCodeDetected(rawValue)
                        }
                    }
                }
                .addOnCompleteListener {
                    imageProxy.close() // Important to close ImageProxy to release the buffer
                }
        } else {
            imageProxy.close()
        }
    }
}

In this conceptual example:

  • QRCodeScannerScreen is the main Composable that handles permissions and result state.
  • CameraPreview is a Composable that wraps an AndroidView to integrate CameraX's PreviewView, as there is no direct Composable for the camera preview yet.
  • BarcodeAnalyzer is a class that implements CameraX's ImageAnalysis.Analyzer and uses ML Kit Barcode Scanning to process camera frames and detect QR codes.
  • The scan result is passed back to the main Composable via a callback.

This modern approach significantly simplifies UI setup and improves code readability and maintainability, leveraging Jetpack Compose's declarative paradigm and the advanced capabilities of ML Kit and CameraX.

Legacy Approach with XML

In this entry we will see how to create a QR code reader in Android in our applications; to get a clearer idea of how a QR reader works, there are currently several QR readers in Android that you can find on Google Play and in this section, we are going to implement one, although, for readers, you can use the camera app.

Developing a QR reader in our Android application with the Play Service library

As you well know, Google offers a significant amount of functionalities that translate into several APIs developed by Google itself that it makes available to us developers through its libraries, as well as Play Services and support, among many others; to do the most common tasks such as creating an application to read a QR code, it is not necessary to use a third-party library or package; the one provided by Google, which does all the work for us, is enough.

Update Note: The `com.google.android.gms:play-services-vision` library has been deprecated. For new implementations, Google recommends using ML Kit Barcode Scanning (`com.google.mlkit:barcode-scanning` or `com.google.android.gms:play-services-mlkit-barcode-scanning` for additional Google Play Services capabilities) in combination with CameraX for camera integration.

Creating a custom QR reader (created by ourselves using Android Studio as a software development environment or IDE) is quite simple with Android and we only need to include an external library which corresponds to Google Play services: com.google.android.gms:play-services-vision, but before that let's see what it is, how a QR code works and how we can use it in our Android applications and websites:

QR Scanner: How to decipher a QR code?

With the help of a mobile phone we can retrieve this information just by pointing the camera at the QR code and that is the idea and what we will do in the next section of this entry where we will dedicate ourselves to detailing how to create a QR reader with Android.

Developing our own QR code reader in Android

Finally we come to the area of interest which is to create our QR code reader in Android; first we must add the necessary dependency in our build.gradle.

Adding the dependency in the build.gradle file in Android Studio

We open our build.gradle file and add the following dependency:

implementation 'com.google.android.gms:play-services:15.0.2'

Update Note: The play-services dependency is too broad. It is better to use only the necessary modules. Also, version 15.0.2 is very old. The Google Play Services Vision library is deprecated. If you insist on using a similar version of Vision API, you would look for a more recent and specific one like com.google.android.gms:play-services-vision:X.Y.Z (where X.Y.Z is a recent version before its total deprecation) or, even better, migrate to ML Kit Barcode Scanning and CameraX.

The previous dependency gives us access not only to the vision API but to the entire platform that Google Play offers us, as in our case of interest is only to read a QR code, we can use the following dependency in our build.gradle:

implementation 'com.google.android.gms:play-services-vision:15.0.2'

You must keep in mind that versions change from one day to the next; you can find the latest version in the official documentation at the following link:

Set Up Google Play Services

Update Note: Using 15.+ for dependency versions is not a recommended practice in production projects, as it can introduce unexpected compatibility and compilation issues. It is better to specify an exact version or use ranges with caution.

For the package to update automatically we could do something like implementation 'com.google.android.gms:play-services-vision:15.+'

Configuring the rest of the application for our QR reader

The manifest file to handle permissions

In our manifest we place the use of the camera:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="net.desarrollolibre.qr.qr">
    <uses-permission android:name="android.permission.CAMERA" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

The view of our QR code reader

With our dependency already configured, we can now go to our activity and/or fragment and create the necessary objects, events and configurations to have our own QR reader; but first we must configure the layout of the activity or fragment:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_gravity="center_horizontal"
        android:padding="8dp">
        <SurfaceView
            android:id="@+id/camera_view"
            android:layout_width="640px"
            android:layout_height="480px"
            android:layout_alignParentLeft="true"
            android:layout_centerVertical="true" />
    </LinearLayout>
</LinearLayout>

Configuring the code reader and camera in our activity

And in our activity or fragment we must create a BarcodeDetector object. This class allows recognizing barcodes and QR codes - which is our case of interest - through the device's camera and returning a SparseArray object with the decoded data of the analyzed QR. In addition to all this, we create the camera source and the associated resolution:

        // create the qr detector
        barcodeDetector =
                new BarcodeDetector.Builder(getContext())
                        .setBarcodeFormats(Barcode.QR_CODE)
                        .build();
        // create the camera source
        cameraSource = new CameraSource
                .Builder(getContext(), barcodeDetector)
                .setRequestedPreviewSize(640, 480)
                .build();

The CameraSource object to get camera frames

We specify the SurfaceView object of our layout; this object is responsible for mirroring or reflecting what we are seeing through the device's camera on our layout surface:

cameraView = (SurfaceView) v.findViewById(R.id.camera_view);

Now we specify the listeners that allow controlling the camera's life cycle:

        // camera life cycle listener
        cameraView.getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                // verify if the user granted camera permissions
                if (ContextCompat.checkSelfPermission(getContext(), android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
                    try {
                        cameraSource.start(cameraView.getHolder());
                    } catch (IOException ie) {
                        Log.e("CAMERA SOURCE", ie.getMessage());
                    }
                } else {
                    Toast.makeText(getContext(), getResources().getString(R.string.error_camara), Toast.LENGTH_SHORT).show();
                }
            }
            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            }
            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                cameraSource.stop();
            }
        });

An important point of the previous code is to start the camera once our surface is drawn with cameraSource.start(cameraView.getHolder());.

Finally, the event that will return the result provided by the QR detection and processing is prepared; when there is a response, barcodes.size() will return a value greater than zero:

 // prepare the QR detector
        barcodeDetector.setProcessor(new Detector.Processor<Barcode>() {
            @Override
            public void release() {
            }
            @Override
            public void receiveDetections(Detector.Detections<Barcode> detections) {
                final SparseArray<Barcode> barcodes = detections.getDetectedItems();
                if (barcodes.size() != 0) {
barcodes.valueAt(0).displayValue.toString();
                 // do something
            }
barcodeDetector.release();
        });

With barcodes.valueAt(0).displayValue.toString(); we obtain the data returned by the QR, which can be text, an integer or a URL depending on how we configure our QR code.

Permissions in Android 6 and above

Update Note: The ActivityCompat and AppCompatActivity classes in the original code are from the support libraries (android.support.*). In modern projects, these have been replaced by AndroidX (androidx.core.app.ActivityCompat and androidx.appcompat.app.AppCompatActivity, respectively). Permission handling follows a similar pattern, but it is always good to consult the latest AndroidX documentation.

As of Android 6, Google created a new permission scheme which is requested from the user when they need to use a function of said permission(s)...

...therefore, it is not enough to just include the dependency in our AndroidManifest, we must also request the permission itself from the Java code of our Android application.

So, when building our surface we place the following code:

// camera life cycle listener
cameraView.getHolder().addCallback(new SurfaceHolder.Callback() {
   @Override
   public void surfaceCreated(SurfaceHolder holder) {
       // verify if the user granted camera permissions
       if (ActivityCompat.checkSelfPermission(QRActiviy.this, Manifest.permission.CAMERA)
               != PackageManager.PERMISSION_GRANTED) {
           if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
               // we check the Android version to be at least M to show
               // the camera request dialog
               if (shouldShowRequestPermissionRationale(
                       Manifest.permission.CAMERA)) ;
               requestPermissions(new String[]{Manifest.permission.CAMERA},
                       MY_PERMISSIONS_REQUEST_CAMERA);
           }
           return;
       } else {
           try {
               cameraSource.start(cameraView.getHolder());
           } catch (IOException ie) {
               Log.e("CAMERA SOURCE", ie.getMessage());
           }
       }
   }
   @Override
   public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
   }
   @Override
   public void surfaceDestroyed(SurfaceHolder holder) {
       cameraSource.stop();
   }
});

The code is relatively simple, we verify if the permission was granted with checkSelfPermission and with shouldShowRequestPermissionRationale we show the dialog to request the permission.

Finally the code looks like this:

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.support.v4.app.ActivityCompat; // Use androidx.core.app.ActivityCompat
import android.support.v7.app.AppCompatActivity; // Use androidx.appcompat.app.AppCompatActivity
import android.os.Bundle;
import android.util.Log;
import android.util.SparseArray;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.webkit.URLUtil;
import com.google.android.gms.vision.CameraSource; // Deprecated, use ML Kit Barcode Scanning
import com.google.android.gms.vision.Detector; // Deprecated, use ML Kit Barcode Scanning
import com.google.android.gms.vision.barcode.Barcode; // Deprecated, use ML Kit Barcode Scanning
import com.google.android.gms.vision.barcode.BarcodeDetector; // Deprecated, use ML Kit Barcode Scanning
import java.io.IOException;
public class MainActivity extends AppCompatActivity {
    private CameraSource cameraSource;
    private SurfaceView cameraView;
    private final int MY_PERMISSIONS_REQUEST_CAMERA = 1;
    private String token = "";
    private String tokenanterior = "";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        cameraView = (SurfaceView) findViewById(R.id.camera_view);
        initQR();
    }
    public void initQR() {
        // create the qr detector
        BarcodeDetector barcodeDetector =
                new BarcodeDetector.Builder(this)
                        .setBarcodeFormats(Barcode.ALL_FORMATS)
                        .build();
        // create the camera
        cameraSource = new CameraSource
                .Builder(this, barcodeDetector)
                .setRequestedPreviewSize(1600, 1024)
                .setAutoFocusEnabled(true) //you should add this feature
                .build();
        // camera life cycle listener
        cameraView.getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                // verify if the user granted camera permissions
                if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA)
                        != PackageManager.PERMISSION_GRANTED) {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                        // we check the Android version to be at least M to show
                        // the camera request dialog
                        if (shouldShowRequestPermissionRationale(
                                Manifest.permission.CAMERA)) ;
                        requestPermissions(new String[]{Manifest.permission.CAMERA},
                                MY_PERMISSIONS_REQUEST_CAMERA);
                    }
                    return;
                } else {
                    try {
                        cameraSource.start(cameraView.getHolder());
                    } catch (IOException ie) {
                        Log.e("CAMERA SOURCE", ie.getMessage());
                    }
                }
            }
            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            }
            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                cameraSource.stop();
            }
        });
        // prepare the QR detector
        barcodeDetector.setProcessor(new Detector.Processor<Barcode>() {
            @Override
            public void release() {
            }
            @Override
            public void receiveDetections(Detector.Detections<Barcode> detections) {
                final SparseArray<Barcode> barcodes = detections.getDetectedItems();
                if (barcodes.size() > 0) {
                    // get the token
                    token = barcodes.valueAt(0).displayValue.toString();
                    // verify that the previous token is not equal to the current one
                    // this is useful to avoid multiple calls using the same token
                    if (!token.equals(tokenanterior)) {
                        // save the last processed token
                        tokenanterior = token;
                        Log.i("token", token);
                        if (URLUtil.isValidUrl(token)) {
                            // if it is a valid URL, open the browser
                            Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(token));
                            startActivity(browserIntent);
                        } else {
                            // share in other apps
                            Intent shareIntent = new Intent();
                            shareIntent.setAction(Intent.ACTION_SEND);
                            shareIntent.putExtra(Intent.EXTRA_TEXT, token);
                            shareIntent.setType("text/plain");
                            startActivity(shareIntent);
                        }
                        new Thread(new Runnable() {
                            public void run() {
                                try {
                                    synchronized (this) {
                                        wait(5000);
                                        // clear the token
                                        tokenanterior = "";
                                    }
                                } catch (InterruptedException e) {
                                    Log.e("Error", "Waiting didn't work!!"); // Corrected message
                                    e.printStackTrace();
                                }
                            }
                        }).start();
                    }
                }
            }
        });
    }
}

QR code reader for Kotlin

All the previous implementation but instead of Java it would be for Kotlin, all our code would look like this:

 private var cameraSource: CameraSource? = null // NULL corrected to null
    private var cameraView: SurfaceView? = null // NULL corrected to null
    private val MY_PERMISSIONS_REQUEST_CAMERA = 1
    private var token = ""
    private var tokenanterior = ""
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        cameraView = findViewById<View>(R.id.camera_view)
        initQR()
    }
    fun initQR() {
        // create the qr detector
        val barcodeDetector = BarcodeDetector.Builder(this)
                .setBarcodeFormats(Barcode.ALL_FORMATS)
                .build()
        // create the camera
        cameraSource = CameraSource.Builder(this, barcodeDetector)
                .setRequestedPreviewSize(1600, 1024)
                .setAutoFocusEnabled(true) //you should add this feature
                .build()
        // camera life cycle listener
        cameraView!!.holder.addCallback(object : SurfaceHolder.Callback {
            override fun surfaceCreated(holder: SurfaceHolder) {
                // verify if the user granted camera permissions
                if (ActivityCompat.checkSelfPermission(this@MainActivity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                        // we check the Android version to be at least M to show
                        // the camera request dialog
                        if (shouldShowRequestPermissionRationale(
                                        Manifest.permission.CAMERA))
                        ;
                        requestPermissions(arrayOf(Manifest.permission.CAMERA),
                                MY_PERMISSIONS_REQUEST_CAMERA)
                    }
                    return
                } else {
                    try {
                        cameraSource!!.start(cameraView!!.holder)
                    } catch (ie: IOException) {
                        Log.e("CAMERA SOURCE", ie.message)
                    }
                }
            }
            override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {}
            override fun surfaceDestroyed(holder: SurfaceHolder) {
                cameraSource!!.stop()
            }
        })
        // prepare the QR detector
        barcodeDetector.setProcessor(object : Detector.Processor<Barcode> { // Añadido override
            override fun release() {} // Añadido override
            override fun receiveDetections(detections: Detector.Detections<Barcode>) { // Añadido override
                val barcodes = detections.getDetectedItems()
                if (barcodes.size() > 0) { // Movido el acceso a barcodes.valueAt(0) dentro del if
                    // get the token
                    token = barcodes.valueAt(0).displayValue.toString()
                    // verify that the previous token is not equal to the current one
                    // this is useful to avoid multiple calls using the same token
                    if (token != tokenanterior) {
                        // save the last processed token
                        tokenanterior = token
                        Log.i("token", token)
                        if (URLUtil.isValidUrl(token)) {
                            // if it is a valid URL, open the browser
                            val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(token))
                            startActivity(browserIntent)
                        } else {
                            // share in other apps
                            val shareIntent = Intent()
                            shareIntent.action = Intent.ACTION_SEND
                            shareIntent.putExtra(Intent.EXTRA_TEXT, token)
                            shareIntent.type = "text/plain"
                            startActivity(shareIntent)
                        }
                        Thread(object : Runnable {
                            override fun run() {
                                try {
                                    synchronized(this) {
                                        wait(5000)
                                        // clear the token
                                        tokenanterior = ""
                                    }
                                } catch (e: InterruptedException) {
                                    Log.e("Error", "Waiting didn't work!!") // Corrected message
                                    e.printStackTrace()
                                }
                            }
                        }).start()
                    }
                }
            }
        })
    }

In the previous code, you can notice that when a QR code is detected by the mobile, a series of steps are performed, ranging from obtaining it using barcodes.valueAt(0).displayValue.toString();, validation (and release with the Thread) to avoid reading the same QR consecutively (the QR reader always continues running regardless of whether it has processed a QR or not) and then using URLUtil.isValidUrl(token) we verify if the obtained token is a URL, and in that case it opens the predefined browser of our phone, otherwise it simply shows the dialog to share content through the different applications we have on our phone.

When running the application, we will get a screen like the following:

app running with qr reader
Screenshot of the application running with the QR reader (legacy approach).

It is worth remembering that once we obtain the code we can do whatever we want, generally if we are creating a custom application (See how to create QR codes in CodeIgniter), it is likely that we want to send it to our application to validate it or simply show it to the user through a dialog or open a browser as we specified above.

You can use the following QR that returns the URL of this Blog:

desarrollolibre qr
Example QR to test the scanner.

An important point is that you can use the same functionality to read barcodes in Android, the same app core is used and in this way you can create a very powerful app that serves as a reader or QR code reader through the implementation of the library and the device's camera.

Next step, learn how to use Room Database (SQLite) in Android Studio with Kotlin

It explains what a QR is, how to generate a QR and how to program, configure and recognize our own QR code reader or scanner with Android using Android Studio as an IDE.

I agree to receive announcements of interest about this Blog.

Andrés Cruz

ES En español