ZXing Android Embedded tutorial – integrating a barcode scanner in Android Studio

Kevin August 8, 2024 12 mins read
app store

ZXing is an open-source project that provides a barcode image processing library. ZXing Android Embedded is a standalone library that facilitates barcode scanning within Android applications.

In this tutorial, we’ll show you how to use ZXing Android Embedded to integrate a barcode scanner into your Android app using Kotlin and Android Studio.

To achieve this, we’ll do the following:

  1. Create a new Android project
  2. Add the ZXing dependencies
  3. Create the app layout
  4. Configure the app activity
  5. Change the orientation
  6. Run the app

Let’s get started! 🧑🏻‍💻

Step 1: Create a new Android project

To create a new Android project, launch Android Studio and follow the steps below:

  1. On the welcome screen, click on New Project. If Android Studio is already open, on the top menu, select File > New > New Project.
  2. On the new window, select Phone and Tablet and choose the Empty Views Activity. Click Next.
  3. Set the following project configurations:
    1. Name: Choose a name for your app. In this tutorial, we’ll go with BarcodeScanner.
    2. Package Name: This field is automatically populated based on your project name.
    3. Save Location: Select a directory to save your project.
    4. Language: Choose the programming language (Java or Kotlin). To follow this guide, select Kotlin.
    5. Minimum SDK: Select the minimum Android version your app will support. Fewer devices will support the newer versions. For this tutorial, choose API 24(“Nougat”; Android 7.0).
  4. Click Finish to create your project.

When you create the new project, Android Studio will make the necessary configurations and build the modules. After this is finished, you should see a project with an organization similar to the one below.

Step 2: Add the ZXing dependencies

To start, you have to add ZXing to your project dependencies. First, add ZXing to your project’s libraries:

  1. Open the libs.versions.toml file.
  2. Under [versions] add scanner = "4.3.0".
  3. Under [libraries] add scanner = { group = "com.journeyapps", name = "zxing-android-embedded", version.ref = "scanner" }. Here, you are including the library and naming it as scanner, which you will use to add to your project.

The libs.versions.tom file should look something like this:

[versions]
agp = "8.4.0"
kotlin = "1.9.0"
coreKtx = "1.13.1"
junit = "4.13.2"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
appcompat = "1.7.0"
material = "1.12.0"
activity = "1.9.0"
scanner = "4.3.0" 
constraintlayout = "2.1.4"

[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
scanner = { group = "com.journeyapps", name = "zxing-android-embedded", version.ref = "scanner" }


[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }

Afterwards, you have to include the library in your project’s build.gradle.kts (Modular :app) using the scanner name:

  1. Open the build.gradle.kts (Modular :app) file.
  2. Add implementation(libs.scanner) within dependencies.

The build.gradle.kts (Modular :app) file should look like this:

plugins {
   alias(libs.plugins.android.application)
   alias(libs.plugins.jetbrains.kotlin.android)
}

android {
   namespace = "com.example.barcodescanner"
   compileSdk = 34

   defaultConfig {
       applicationId = "com.example.barcodescanner"
       minSdk = 24
       targetSdk = 34
       versionCode = 1
       versionName = "1.0"

       testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
   }

   buildTypes {
       release {
           isMinifyEnabled = false
           proguardFiles(
               getDefaultProguardFile("proguard-android-optimize.txt"),
               "proguard-rules.pro"
           )
       }
   }
   compileOptions {
       sourceCompatibility = JavaVersion.VERSION_1_8
       targetCompatibility = JavaVersion.VERSION_1_8
   }
   kotlinOptions {
       jvmTarget = "1.8"
   }
}

dependencies {

   implementation(libs.androidx.core.ktx)
   implementation(libs.androidx.appcompat)
   implementation(libs.material)
   implementation(libs.androidx.activity)
   implementation(libs.androidx.constraintlayout)
   implementation(libs.scanner)
   testImplementation(libs.junit)
   androidTestImplementation(libs.androidx.junit)
   androidTestImplementation(libs.androidx.espresso.core)
}

Step 3: Create the app layout

Before using the functionalities provided by the ZXing library, you need to add the visual elements to your app to enable the user to interact with it. For this tutorial, the app is composed of two elements:

  • A Button the user will use to start the scanning process.
  • A TextView to display the bar code after finishing the scanning process.

To add the above components to your app, open the activity_main.xml file, which you will find under app > res > layout. Replace the existing content with the following code.

💡 If you open the activity_main.xml file and no code appears on your screen, click the Split button at the top right of Android Studio.

<?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:id="@+id/main"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:background="@android:color/white"
   tools:context=".MainActivity">

   <!-- Button for starting the barcode scanning process -->
   <Button
       android:id="@+id/scanBtn"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="Scan Barcode"
       app:layout_constraintBottom_toBottomOf="parent"
       android:layout_marginBottom="250dp"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintHorizontal_bias="0.5"
       app:layout_constraintStart_toStartOf="parent" />

   <!-- TextView to display the scanned barcode value -->
   <TextView
       android:id="@+id/scannedValue"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="Scanned Value"
       android:textColor="@android:color/black"
       android:textSize="20sp"
       android:textStyle="bold"
       app:layout_constraintTop_toBottomOf="@id/scanBtn"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

The configurations used for each component are described in the following tables.

Button
AttributeDescription
android:id="@+id/scanBtn"Unique identifier for the Button.
android:layout_width="wrap_content"The width of the Button is set to wrap its content.
android:layout_height="wrap_content"The height of the Button is set to wrap its content.
android:text="Scan Bar Code"The text displayed on the Button.
app:layout_constraintBottom_toBottomOf="parent"Constraint to align the bottom of the Button with the bottom of the parent layout.
android:layout_marginBottom="250dp"Margin at the bottom of the Button.
app:layout_constraintEnd_toEndOf="parent"Constraint to align the end (right) of the Button with the end (right) of the parent layout.
app:layout_constraintHorizontal_bias="0.5"Horizontal bias to center the Button horizontally.
app:layout_constraintStart_toStartOf="parent"Constraint to align the start (left) of the Button with the start (left) of the parent layout.
TextView
AttributeDescription
android:id="@+id/scannedValue"Unique identifier for the TextView.
android:layout_width="wrap_content"The width of the TextView is set to wrap its content.
android:layout_height="wrap_content"The height of the TextView is set to wrap its content.
android:text="Scanned Value"The text displayed in the TextView.
android:textColor="@android:color/black"The color of the text is set to black.
android:textSize="20sp"The size of the text is set to 20sp (scaled pixels).
android:textStyle="bold"The text style is set to bold.
app:layout_constraintTop_toBottomOf="@id/scanBtn"Constraint to align the top of the TextView with the bottom of the Button.
app:layout_constraintBottom_toBottomOf="parent"Constraint to align the bottom of the TextView with the bottom of the parent layout.
app:layout_constraintEnd_toEndOf="parent"Constraint to align the end (right) of the TextView with the end (right) of the parent layout.
app:layout_constraintStart_toStartOf="parent"Constraint to align the start (left) of the TextView with the start (left) of the parent layout.

After setting up the above configuration on the activity_main.xml file, Android Studio should present an app preview.

Step 4: Configure the app activity

After adding the components, it’s time to configure the app functionalities. At this step, you will define the functions that define the Button and TextView behaviour.

To start, open the MainActivity.kt file, which you will find under app > kotling+java > com.example.barcodescanner.

First, declare a Button and a TextView variable within the MainActivity class. Then, override the onCreate function to assign the Button and TextView variables to respective Ids defined in the activity_main.xml file. After that, add the registerUiListener() function, which you will define next. The beginning of the MainActivity class should be equal to the content of the following code block:

class MainActivity : AppCompatActivity() {

    private lateinit var scanBtn: Button
    private lateinit var scannedValue: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        scanBtn = findViewById(R.id.scanBtn)
        scannedValue = findViewById(R.id.scannedValue)

        registerUiListener()
    }

Next, you have to define the registerUiListener() function. The registerUiListener method sets up an OnClickListener for the scanBtn button. When someone clicks the button, it triggers the scannerLauncher to launch the barcode scanner. At this point, you can specify the options to define the scanner behaviour. Check ScanOptions.java to see all the available options. This tutorial uses the following options:

  • setPrompt("Scan Barcode"): Specifies the text that will appear when the app accesses the camera to scan the barcode.
  • .setDesiredBarcodeFormats(ScanOptions.ONE_D_CODE_TYPES): Defines that only one-dimensional codes will be identified.

The following code block presents the registerUiListener() code.

private fun registerUiListener() {
   scanBtn.setOnClickListener {
       scannerLauncher.launch(
           ScanOptions().setPrompt("Scan Barcode")
               .setDesiredBarcodeFormats(ScanOptions.ONE_D_CODE_TYPES)
       )
   }
}

Last, you need to define the behaviour of the TextView after a barcode is scanned. For this, you will add the scannerLauncher activity, which will handle the result of the barcode scanning activity. Use the registerForActivityResult method to register it with a ScanContract. You also have to specify the callback function to handle the scan result. In this tutorial, the callback has the following behaviour:

  • The app displays a Canceled message if the scan was cancelled, meaning no content was scanned.
  • If the scan is successful, the app updates the scannedValue TextView with the scanned barcode contents, formatted as Scanned Value: [result].

The following code block presents the scannerLauncher code.

private val scannerLauncher = registerForActivityResult<ScanOptions, ScanIntentResult>(
   ScanContract()
) { result ->

   if (result.contents == null) {
       Toast.makeText(this, "Cancelled", Toast.LENGTH_SHORT).show()
   } else {
       scannedValue.text = buildString {
           append("Scanned Value : ")
           append(result.contents)
       }
   }

}

After adding all configurations, the MainActivity.kt file should have the following content:

package com.example.barcodescanner

import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.google.zxing.client.android.Intents.Scan
import com.journeyapps.barcodescanner.CaptureActivity
import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanIntentResult
import com.journeyapps.barcodescanner.ScanOptions

class MainActivity : AppCompatActivity() {

   private lateinit var scanBtn: Button
   private lateinit var scannedValue: TextView

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)

       scanBtn = findViewById(R.id.scanBtn)
       scannedValue = findViewById(R.id.scannedValue)

       registerUiListener()
   }

   private fun registerUiListener() {
       scanBtn.setOnClickListener {
           scannerLauncher.launch(
               ScanOptions().setPrompt("Scan Barcode")
                   .setDesiredBarcodeFormats(ScanOptions.ONE_D_CODE_TYPES)
           )
       }
   }

   private val scannerLauncher = registerForActivityResult<ScanOptions, ScanIntentResult>(
       ScanContract()
   ) { result ->

       if (result.contents == null) {
           Toast.makeText(this, "Cancelled", Toast.LENGTH_SHORT).show()
       } else {
           scannedValue.text = buildString {
               append("Scanned Value : ")
               append(result.contents)
           }
       }

   }
}

Step 5: Change the orientation

By default, the ZXing Android Embedded uses landscape mode. To change this behaviour, you must change the AndroidManifest.xml file, which you find under app > manifests.

In the AndroidManifest.xml file, you have to add a new CaptureActivity to ensure the scanning interface is always in portrait orientation. The following code block presents the code you need to add to the file:

<activity android:name="com.journeyapps.barcodescanner.CaptureActivity"
            android:screenOrientation="portrait"
            tools:replace="screenOrientation"/>

In the above code, the tools:replace="screenOrientation" attribute informs the Android tools that this activity’s screenOrientation attribute should replace any previously declared screenOrientation attribute for this activity.

Step 6: Run the app

After completing the four steps, you can test the app’s final result. To test the app, you can connect your Android phone to your computer and enable the Developer Options, or use Virtual Devices. If you have never tested an app using Android Studio, you can check one of the following guides:

Before running the app, be sure to:

  • Save all files: Click File > Save All.
  • Sync the project: Click File > Sync Project with Gradle Files.

Build the project without any errors:  On the top menu, click Build > Rebuild Project. You should see the BUILD SUCCESSFUL in the Android Studio terminal.

Disadvantages of using the ZXing barcode scanner library

ZXing provides decent performance for basic barcode scanning tasks but sometimes struggles with more challenging scenarios. Its most notable drawbacks as a barcode scanning solution include:

  • Scanning accuracy and speed: ZXing struggles with scanning poorly lit or damaged barcodes. It often exhibits low recognition rates for smaller barcodes and can fail to decode them altogether.
  • Compatibility issues: ZXing may not perform reliably across all devices, particularly newer models. This has led to frustration among developers who require consistent performance across different platforms.
  • Lack of active development: As an open-source project, ZXing has seen limited updates and maintenance in recent years. This stagnation can lead to unresolved bugs and compatibility issues, making it less reliable for commercial applications.
  • Integration complexity: Integrating ZXing into your app can be cumbersome, especially for developers who may not be familiar with its architecture. This can lead to longer development times and increased chances of bugs during implementation.

We developed the Scanbot Barcode Scanner SDK as a commercial solution to help enterprises overcome these hurdles presented by free barcode scanning software. Our goal was to have a developer-friendly solution available for a wide range of platforms that consistently delivers high-quality results – even in challenging circumstances.

You can try it out using our demo app – just scan the barcode with your phone:

You can also take a look at our Android example app for an implementation similar to this ZXing one. Should you have any questions, feel free to reach out to us on Slack or MS Teams or send us an email via sdksupport@scanbot.io