A Complete Guide to Integrating Zebra TC21/26 Scanners with Android Jetpack Compose

Lukas Kristianto
4 min readSep 10, 2024

--

Introduction

Integrating specialized hardware like the Zebra TC21/26 barcode scanner into an Android app built with Jetpack Compose can be quite challenging. When I started this journey, I realized that there were very few resources available online — especially in terms of integrating the Zebra SDK with Android Jetpack Compose. This post aims to bridge that gap and provide a step-by-step guide to help other developers who may be struggling with similar issues. We’ll cover the key aspects of setting up the barcode scanner, registering the profile application, defining a broadcast receiver, and testing the integration effectively.

Step 1: Register Profile Application

The first hurdle was figuring out how to communicate with the Zebra scanner using the DataWedge API, which allows the device to interact with Android applications. Here is the Kotlin code snippet that defines the DataWedgeManager class to manage this integration.

class DataWedgeManager(
private val context: Context,
private val onVersionReceived: (Boolean) -> Unit,
private val onQRCodeScanned: (String) -> Unit
) {
companion object {
const val ACTION_DATAWEDGE = "com.symbol.datawedge.api.ACTION"
const val EXTRA_SET_CONFIG = "com.symbol.datawedge.api.SET_CONFIG"
const val PROFILE_NAME = "Bahtera_Profile"
const val PROFILE_ENABLED = "true"
const val CONFIG_MODE = "CREATE_IF_NOT_EXIST"
const val PLUGIN_NAME_BARCODE = "BARCODE"
const val RESET_CONFIG = "true"
const val INTENT_OUTPUT_ENABLED = "true"
const val INTENT_DELIVERY = "2"
}

fun configureDataWedgeProfile() {
val profileConfig = Bundle().apply {
putString("PROFILE_NAME", PROFILE_NAME)
putString("PROFILE_ENABLED", PROFILE_ENABLED)
putString("CONFIG_MODE", CONFIG_MODE)

val barcodeConfig = Bundle().apply {
putString("PLUGIN_NAME", PLUGIN_NAME_BARCODE)
putString("RESET_CONFIG", RESET_CONFIG)
}
putBundle("PLUGIN_CONFIG", barcodeConfig)

val appConfig = Bundle().apply {
putString("PACKAGE_NAME", context.packageName)
putStringArray("ACTIVITY_LIST", arrayOf("*"))
}
putParcelableArray("APP_LIST", arrayOf(appConfig))
}

val dwIntent = Intent(ACTION_DATAWEDGE).apply {
putExtra(EXTRA_SET_CONFIG, profileConfig)
}
context.sendBroadcast(dwIntent)
}
}

This code sets up the barcode scanning profile, ensuring the Zebra device is configured to communicate properly with the app.

Step 2: Define the Broadcast Receiver

A critical part of this integration is defining a BroadcastReceiver to handle incoming data and notifications from the Zebra device. Unfortunately, most online resources did not cover this, leading to significant confusion. I had to manually explore the DataWedge documentation to find the correct way to implement this.

The BroadcastReceiver listens for specific intents that the Zebra device sends. Without this receiver, your app will not be able to handle scanned barcode data.

private val broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
ACTION_RESULT_NOTIFICATION, ACTION_RESULT -> {
val versionInfo = intent.getBundleExtra(EXTRA_RESULT_GET_VERSION_INFO)
val version = versionInfo?.getString("DATAWEDGE")
onVersionReceived(!version.isNullOrBlank())
}
FILTER_ACTION -> {
val decodedData = intent.getStringExtra("com.symbol.datawedge.data_string")
onQRCodeScanned(decodedData ?: "")
}
}
}
}

fun registerReceiver() {
val filter = IntentFilter().apply {
addAction(ACTION_RESULT_NOTIFICATION)
addAction(ACTION_RESULT)
addAction(FILTER_ACTION)
addCategory(Intent.CATEGORY_DEFAULT)
}
context.registerReceiver(broadcastReceiver, filter)
}

The above receiver code is crucial because it captures all the barcode data scanned by the Zebra TC21/26 device. Defining the receiver ensures that any data scanned by the device is properly handled within the application.

Step 3: Implementing in Jetpack Compose UI

Integrating the Zebra TC21/26 barcode scanner in a Jetpack Compose UI involves managing the lifecycle of the DataWedgeManager and ensuring the receiver is registered and unregistered correctly. This can be achieved using the DisposableEffect API, which allows you to handle side effects like registering and unregistering the receiver based on the lifecycle of the Compose function.

Here’s how you can implement the integration in a Jetpack Compose UI:

@Composable
fun ZebraScannerScreen(
context: Context,
onQRCodeScanned: (String) -> Unit
) {
val lifeCycleOwner = LocalLifecycleOwner.current
var showStatusZebra by remember { mutableStateOf(false) }

// Use DisposableEffect to manage the lifecycle of the DataWedgeManager
DisposableEffect(lifeCycleOwner) {
// Initialize DataWedgeManager
val dataWedgeManager = DataWedgeManager(
context = context,
onVersionReceived = { showStatusZebra = it },
onQRCodeScanned = { decodedData -> onQRCodeScanned(decodedData) }
)

// Register and configure DataWedge
dataWedgeManager.registerReceiver()
dataWedgeManager.configureDataWedgeProfile()
dataWedgeManager.requestDataWedgeVersion()

onDispose {
dataWedgeManager.unregisterReceiver()
}
}

// Display the current status of the Zebra scanner
Text(
text = if (showStatusZebra) "Zebra Scanner Connected" else "Connecting Zebra Scanner...",
modifier = Modifier.padding(16.dp)
)
}

Using DisposableEffect, we ensure that the DataWedgeManager is initialized and properly disposed of based on the lifecycle of the Jetpack Compose UI. This is crucial to avoid memory leaks or unexpected behavior.

Step 4: Testing the Integration

After setting up the profile, defining the broadcast receiver, and implementing the integration in your Jetpack Compose UI, it’s time to test the functionality:

  1. Initialize the DataWedgeManager: Instantiate it within the Composable function as shown.
  2. Check Connection Status: The status message in the UI should update based on the connection to the Zebra scanner.
  3. Perform Test Scans: Use the Zebra TC21/26 scanner to test barcode scanning. Verify that the scanned data is correctly handled by your application.

Conclusion

Integrating the Zebra TC21/26 barcode scanner with an Android Jetpack Compose application can be daunting due to the lack of comprehensive guides. This guide helps you define the broadcast receiver correctly, set up the profile application, and handle the integration within a Jetpack Compose UI. I hope this helps other developers avoid the struggles I faced and achieve a smooth integration.

Feel free to leave a comment if you have any questions or need further assistance!

--

--

Lukas Kristianto
Lukas Kristianto

Written by Lukas Kristianto

Senior Software Engineer Android and Artificial Intelligence

No responses yet