zebra_emdk_plugin
A Flutter plugin for Zebra Android devices that wraps the Zebra EMDK SDK.
Supports barcode scanning, scanner configuration (full decoder params), profile management (MX/StageNow), LED/beep/vibrate notifications, and OEM device info queries.
⚠️ Important: This plugin is Zebra-only. Always gate usage on
await EmdkManager().isSupported()— it returnsfalseon non-Zebra devices (and the plugin stays loaded without crashing).
Features
| Feature | Description |
|---|---|
| EMDK Manager | Initialize the EMDK service and listen for open/close events |
| Barcode Manager | Enumerate/filter scanners, init by friendly name, scanner config, read control, scan data & status events |
| Notification Manager | Enumerate/filter notification devices, trigger LED, beep, and vibrate notifications |
| Profile Manager | Apply MX profiles asynchronously with a serialised queue, query OEM content-provider URIs, map hardware keys |
| Key Event Manager | Listen to physical button presses (P-buttons, triggers, volume keys, etc.) via MX KeyMappingMgr |
Setup
1. Add the Zebra EMDK Maven repository
In your app's android/build.gradle.kts, add:
allprojects {
repositories {
google()
mavenCentral()
maven {
url = uri("https://zebratech.jfrog.io/artifactory/EMDK-Android/")
}
}
}
2. Add the dependency
dependencies:
zebra_emdk_plugin: ^0.4.1
3. Add the EMDK compile-only dependency (required)
EMDK is a device-provided system library — you compile against it but it is never packaged.
Flutter's generated GeneratedPluginRegistrant instantiates this plugin (which references EMDK
types), so your app must have EMDK on its compile classpath. In your app's
android/app/build.gradle.kts:
dependencies {
compileOnly("com.symbol:emdk:11.0.134")
}
Without this, the app fails to build with class file for com.symbol.emdk.EMDKManager not found.
4. Update AndroidManifest.xml
Add the following to your app's android/app/src/main/AndroidManifest.xml:
<manifest ...>
<!-- Required EMDK permissions -->
<uses-permission android:name="com.symbol.emdk.permission.EMDK" />
<uses-permission android:name="com.zebra.provider.READ" />
<!-- Required for Android 11+ package visibility -->
<queries>
<package android:name="com.symbol.emdk.emdkservice" />
<package android:name="com.zebra.zebracontentprovider" />
<provider android:authorities="oem_info" />
</queries>
<application ...>
<uses-library android:name="com.symbol.emdk" android:required="false" />
...
</application>
</manifest>
Usage
Initialize EMDK and scan a barcode
import 'package:zebra_emdk_plugin/zebra_emdk_plugin.dart';
final emdk = EmdkManager();
// 1. Bail out cleanly on non-Zebra devices.
if (!await emdk.isSupported()) return;
// 2. Start listening for the open event before initializing.
emdk.onOpened.listen((_) async {
final bm = emdk.barcodeManager;
// 3. Get all connected scanners and init the first one.
final scanners = await bm.getConnectedScanners();
if (scanners.isNotEmpty) {
await bm.initScanner(scanners.first.friendlyName!);
}
// 4. Listen for scan data and start reading.
bm.onData.listen((collection) {
final barcode = collection.scanData?.firstOrNull?.data;
print('Scanned: $barcode');
});
await bm.enableRead();
});
// 5. Request the EMDK Manager from the OS.
await emdk.initialize();
Trigger a notification (beep + LED + vibrate)
final nm = emdk.notificationManager;
// Init the first connected notification device
final devices = await nm.getConnectedDevices();
if (devices.isNotEmpty) {
await nm.initDevice(devices.first.friendlyName!);
}
await nm.notify(
NotificationCommand(
beep: NotificationBeepParams(pattern: [
NotificationBeep(time: 100, frequency: 2500),
]),
led: NotificationLEDParams(
color: 0x00FF00, onTime: 200, offTime: 200, repeatCount: 3,
),
vibrate: NotificationVibrateParams(time: 500),
),
);
Apply an MX profile
final pm = emdk.profileManager;
pm.onData.listen((result) {
print('Profile result: ${result?.result?.statusCode?.value}');
});
// Pass only the inner <characteristic> block(s) — the plugin wraps them automatically
pm.processProfileAsync('''
<characteristic type="PowerMgr">
<parm name="ResetAction" value="1"/>
</characteristic>
''');
Built-in MX profiles & convenience methods
Common device-control operations are convenience methods on ProfileManager (the MxCommands
extension) — no hand-written XML required:
final pm = emdk.profileManager;
// Paired enable/disable operations are setX...State(bool) setters:
pm.setBluetoothState(true);
pm.setNfcState(false);
pm.setBluetoothPairing(true);
pm.setNfcPairing(false);
// Scanner control, system-app enable/disable, and Zebra Enterprise Keyboard:
pm.resetScanner(); // also: disconnectScanner() / unpairScanner() / locateScanner()
pm.setSystemAppState(package: 'com.google.android.inputmethod.latin', enabled: false);
pm.enableEnterpriseKeyboard(currentLocale: 'pt_PT', showVirtualKeyboard: true);
// Individual UI / display / device-admin setters (true = enabled/visible, false = disabled/hidden):
pm.setStatusBarState(false);
pm.setNavigationBarState(false);
pm.setNotificationPullDownState(false);
pm.setRecentAppButtonState(false);
pm.setLargeScreenTaskbarState(false);
pm.setScreenBrightness(80);
pm.setScreenTimeout(600); // 65535 ≈ never
pm.setAutoRotateState(true);
pm.setScreenLockType(5);
// Launch an activity (implicit via action, or explicit via package/class):
pm.startActivity(actionName: 'android.settings.SETTINGS');
pm.startActivity(package: 'com.acme.app', className: 'com.acme.app.MainActivity');
// Destructive ops are exposed as methods too — guard them behind your own confirmation UI:
pm.reboot();
// pm.fullDeviceWipe(); // erases ALL data — irreversible
Query OEM device info
Each secured OEMinfo URI needs an AccessMgr permission grant before it can be read. Request the
permission once (fire-and-forget; result on onData), then read it with the matching getter:
final pm = emdk.profileManager;
// 1. Request the permissions you need (e.g. once after EMDK is ready).
pm.requestBluetoothMacPermission();
pm.requestBuildSerialPermission();
pm.requestProductModelPermission();
pm.requestWifiMacPermission();
pm.requestPeripheralBatteryInfoPermission();
// 2. Read the values with the named getters.
final btMac = await pm.getBluetoothMac();
final serial = await pm.getBuildSerial();
final model = await pm.getProductModel();
final wifiMac = await pm.getWifiMac();
final battery = await pm.getPeripheralBatteryInfo();
print('BT: $btMac | Serial: $serial | Model: $model');
// Convenience: Zebra Bluetooth pairing barcode derived from the BT MAC.
final pairingCode = await pm.getPairingCode();
// Need a URI that has no named helper? Use the raw calls directly:
// pm.requestServicePermission('content://oem_info/oem.zebra.secure/bt_mac');
// final raw = await pm.resolveCursorUri('content://oem_info/oem.zebra.secure/bt_mac');
Get scanner configuration
final config = await emdk.barcodeManager.getConfig();
// Modify and apply
final updated = ScannerConfig(
ngSimulScanParams: NGSimulScanParams(
ngSimulScanMode: NGSimulScanMode.multiBarcode,
multiBarcodeParams: NGSimulScanMultiBarcodeParams(barcodeCount: 2),
),
);
await emdk.barcodeManager.setConfig(updated);
API Reference
EmdkManager
| Member | Description |
|---|---|
isSupported() |
true only on a Zebra device with EMDK present; safe (never throws) on any device |
initialize() |
Requests the EMDK Manager from the OS |
dispose() |
Releases all EMDK resources |
barcodeManager |
Lazy getter for BarcodeManager |
notificationManager |
Lazy getter for NotificationManager |
profileManager |
Lazy getter for ProfileManager |
keyEventManager |
Lazy getter for KeyEventManager |
onOpened |
Stream fired when EMDK is ready |
onClosed |
Stream fired when EMDK is closed |
BarcodeManager
| Member | Description |
|---|---|
getSupportedScanners() |
All known scanners (connected or not) |
getConnectedScanners() |
Currently-connected scanners only |
initScanner(friendlyName) |
Initializes a scanner by friendly name |
deinitScanner() |
Releases the active scanner |
getScannerInfo() |
Info about the active scanner |
enableRead() |
Arms the scanner to read |
disableRead() |
Cancels a pending read |
getConfig() |
Returns the current ScannerConfig |
setConfig(config) |
Applies a ScannerConfig |
onConnectionChange |
Stream of scanner connect/disconnect events |
onStatus |
Stream of scanner state changes |
onData |
Stream of ScanDataCollection scan results |
NotificationManager
| Member | Description |
|---|---|
getSupportedDevices() |
All known notification devices |
getConnectedDevices() |
Currently-connected notification devices only |
initDevice(friendlyName) |
Enables a notification device by friendly name |
deinitDevice() |
Disables the active notification device |
getDeviceInfo() |
Info about the active notification device |
notify(command) |
Triggers a NotificationCommand (LED/Beep/Vibrate) |
ProfileManager
| Member | Description |
|---|---|
processProfileAsync(characteristics) |
Queues an MX XML characteristics block for processing |
requestServicePermission(uri) |
Requests an AccessMgr content-provider permission |
request{BluetoothMac,BuildSerial,ProductModel,WifiMac,PeripheralBatteryInfo}Permission() |
Named requestServicePermission helpers, one per OEMinfo URI; pair with the matching get…() reader |
resolveCursorUri(uri) |
Reads a single-value Zebra OEMinfo URI |
get{BluetoothMac,BuildSerial,ProductModel,WifiMac,PeripheralBatteryInfo}() |
Named resolveCursorUri readers for the common OEMinfo URIs |
getPairingCode() |
Zebra Bluetooth pairing barcode payload derived from the BT MAC |
addKeyListener(key) |
Maps a KeyIdentifier key to send a KEY_DOWN_EVENT broadcast |
resetAllKeyMappings() |
Resets all key mappings to factory defaults |
onData |
Stream of ProfileResultData results |
KeyEventManager
| Member | Description |
|---|---|
onKeyDown |
Stream of KeyIdentifier? emitted on every mapped key press |
KeyIdentifier
Enum with ~100 named Zebra hardware keys. Common values:
| Value | Key |
|---|---|
KeyIdentifier.p1 … p6 |
Programmable P-buttons |
KeyIdentifier.scan |
Scanner trigger |
KeyIdentifier.leftTrigger1, rightTrigger1 |
Side trigger buttons |
KeyIdentifier.volumeUp, volumeDown |
Volume keys |
KeyIdentifier.f1 … f12 |
Function keys |
KeyIdentifier.enter, back, home |
System/navigation keys |
See KeyIdentifier in key_identifiers.dart for the full list.
Listen to hardware key presses
// 1. Map the keys you want to listen to (call after EMDK is ready)
emdk.profileManager.resetAllKeyMappings(); // optional: start clean
emdk.profileManager.addKeyListener(KeyIdentifier.p1);
emdk.profileManager.addKeyListener(KeyIdentifier.scan);
// 2. Subscribe to key-down events
emdk.keyEventManager.onKeyDown.listen((key) {
if (key == KeyIdentifier.p1) print('P1 pressed');
if (key == KeyIdentifier.scan) print('Scan pressed');
});
Error Handling
Manager calls throw a typed Dart exception when the native EMDK reports a known error:
| Dart exception | When thrown |
|---|---|
ScannerException |
A barcode scanner operation fails (e.g. initScanner, enableRead, setConfig) |
NotificationException |
A notification device operation fails (e.g. initDevice, notify) |
EMDKException |
The EMDK manager fails to open or a feature instance cannot be acquired |
PlatformException |
Any unexpected native exception not mapped to an EMDK type |
try {
await emdk.barcodeManager.initScanner(friendlyName);
} on ScannerException catch (e) {
print('Scanner error: ${e.result.value} — ${e.message}');
} on PlatformException catch (e) {
print('Native error: ${e.code} — ${e.message}');
}
Platform Support
| Platform | Supported |
|---|---|
| Android (Zebra devices) | ✅ |
| iOS | ❌ |
| Web | ❌ |
| Windows / macOS / Linux | ❌ |
Minimum Android SDK: 24 (Android 7.0)
EMDK Version
Built against EMDK 11.0.134 — the latest version published to the Zebra EMDK Maven repository.
License
MIT — see LICENSE.
Libraries
- data/connection_change
- data/decoder_params
- data/emdk_manager
- data/key_identifiers
- data/notification
- data/scan_data
- data/scanner
- data/scanner_config
- mx_commands
- mx_profiles
- services/barcode_manager
- services/emdk_manager
- services/key_event_manager
- services/notification_manager
- services/profile_manager
- zebra_content_uris
- zebra_emdk_plugin
- Pure Zebra EMDK plugin — a faithful, Pigeon-backed wrapper around the Zebra EMDK for Android (barcode scanning, scanner config, MX profiles, OEM info, hardware keys, and LED/beep/vibrate notifications).