A demo Android application that integrates Zebra barcode scanners with WebView using DataWedge, allowing scanned data to be injected into web pages via JavaScript.
Note
You can see this repository for native Android app (Kotlin and Jetpack Compose) that uses the same DataWedge integration: ZebraScanner
The app loads https://zebra.stankovic.cz/ and injects scanned barcode data into the web page using
the window.onBarcodeScanned(data) JavaScript function.
app/src/main/java/com/stankovic/zebrawebview/
├── MainActivity.kt # Main activity orchestrating the app
├── screens/
│ └── ScannerWebView.kt # Compose WebView component
├── viewmodel/
│ └── ScannerViewModel.kt # State management for scanned data
├── scanning/
│ └── ScanBroadcastReceiver.kt # Handles DataWedge broadcast intents
├── config/
│ └── ScanningConfig.kt # Scanner configuration constants
└── ui/theme/ # Material3 theme files
The app follows MVVM architecture with reactive state management:
- Scanner → App: DataWedge sends broadcast intent → ScanBroadcastReceiver → ScannerViewModel
- App → WebView: StateFlow triggers LaunchedEffect → JavaScript injection → Web page
- Create a new Compose project
Add required permissions to AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
<permission android:name="com.stankovic.zebrawebview.SCAN_PERMISSION" android:protectionLevel="signature" />// config/ScanningConfig.kt
object ScanningConfig {
const val APP_SCANNER_INTENT = "com.stankovic.zebrawebview.scan"
const val SCAN_DATA_KEY = "com.symbol.datawedge.data_string"
}// viewmodel/ScannerViewModel.kt
class ScannerViewModel : ViewModel() {
private val _scannedData = MutableStateFlow<String?>(null)
val scannedData: StateFlow<String?> = _scannedData.asStateFlow()
fun updateScannedData(data: String) {
_scannedData.value = data
}
fun clearScannedData() {
_scannedData.value = null
}
}// scanning/ScanBroadcastReceiver.kt
class ScanBroadcastReceiver(
private val scannerViewModel: ScannerViewModel
) : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
intent?.let { receivedIntent ->
if (receivedIntent.action == ScanningConfig.APP_SCANNER_INTENT) {
val scannedData = receivedIntent.getStringExtra(ScanningConfig.SCAN_DATA_KEY)
scannedData?.let { data ->
scannerViewModel.updateScannedData(data)
}
}
}
}
}// screens/ScannerWebView.kt
@Composable
@SuppressLint("SetJavaScriptEnabled")
fun ScannerWebView(
scannerViewModel: ScannerViewModel,
activity: MainActivity,
) {
val scannedData by scannerViewModel.scannedData.collectAsState()
val context = LocalContext.current
val webView = remember {
WebView(context).apply {
settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
cacheMode = WebSettings.LOAD_DEFAULT
setSupportZoom(false)
builtInZoomControls = false
displayZoomControls = false
useWideViewPort = false
loadWithOverviewMode = true
layoutAlgorithm = WebSettings.LayoutAlgorithm.NORMAL
}
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
)
loadUrl("https://your-website.com/")
}
}
// Inject scanned data into WebView
LaunchedEffect(scannedData) {
scannedData?.let { data ->
webView.evaluateJavascript(
"if (window.onBarcodeScanned) window.onBarcodeScanned('$data');",
null
)
}
}
Scaffold(
modifier = Modifier.systemBarsPadding(),
) { paddingValues ->
AndroidView(
factory = { webView },
modifier = Modifier
.fillMaxSize()
.padding(paddingValues),
)
}
}// MainActivity.kt
class MainActivity : ComponentActivity() {
private lateinit var scannerReceiver: ScanBroadcastReceiver
private lateinit var scannerViewModel: ScannerViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
scannerViewModel = ViewModelProvider(this)[ScannerViewModel::class.java]
scannerReceiver = ScanBroadcastReceiver(scannerViewModel)
enableEdgeToEdge()
setContent {
YourAppTheme {
ScannerWebView(
scannerViewModel = scannerViewModel,
activity = this@MainActivity,
)
}
}
}
override fun onResume() {
super.onResume()
registerScannerReceiver()
}
override fun onPause() {
super.onPause()
unregisterReceiver(scannerReceiver)
}
@SuppressLint("UnspecifiedRegisterReceiverFlag")
private fun registerScannerReceiver() {
val filter = IntentFilter().apply {
addCategory(Intent.CATEGORY_DEFAULT)
addAction(ScanningConfig.APP_SCANNER_INTENT)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(scannerReceiver, filter, RECEIVER_EXPORTED)
} else {
registerReceiver(scannerReceiver, filter)
}
}
}Add the receiver to AndroidManifest.xml:
<receiver android:name=".scanning.ScanBroadcastReceiver" android:exported="true"
android:permission="com.stankovic.zebrawebview.SCAN_PERMISSION">
<intent-filter>
<action android:name="com.stankovic.zebrawebview.scan" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>Important
You have to create a profile after installing this app on the Zebra device!
- Open DataWedge app on the device.
- Click on three dots in top right corner
- Click on
New Profile - Enter any name of your profile
- Click on the profile you just created
- Ensure that the profile is enabled (should be by default)
- Associate that profile with the sample application
- Click on
Associated apps - Click on three dots in top right corner and click on
New app/activity - Scroll in the menu, find
com.stankovic.zebrawebviewand click on it. - In the
Select activitymenu choose* - Go back to the profile screen
- Click on
- Ensure that
Barcode inputis enabled - Ensure that
Keystroke outputis enabled - Scroll down to
Intent outputsection - Click on checkbox to enable it
- Click on
Intent actionand entercom.stankovic.zebrawebview.scanaction and click OK.
- Intent action must be same as set in
ScanningConfig.ktinAPP_SCANNER_INTENTconstant.
- Click on
Intent deliveryand chooseBroadcast intent
Your web page must implement the JavaScript callback:
// In your web page
window.onBarcodeScanned = (data) => {
console.log("Scanned data:", data);
}- Test with manual intent broadcasts using ADB:
adb shell am broadcast -a com.stankovic.zebrawebview.scan --es com.symbol.datawedge.data_string "test123"This project is provided as a demo for educational purposes.
Feel free to submit issues and pull requests to improve this demo.