@karus-systems/react-native-honeywell-intent
v0.1.0
Published
React Native + Expo library for Honeywell barcode scanners using the Data Collection Intent API — no Mobility SDK binary required.
Maintainers
Readme
react-native-honeywell-intent
Honeywell barcode scanning for React Native and Expo, over Honeywell's Data Collection Intent API — claim the scanner, configure symbologies, and receive decoded barcodes entirely through Android broadcasts.
No DataCollection.jar. No DataCollection.aar. No vendored binary.
That is the point of this library. Every other React Native Honeywell package commits Honeywell's Mobility SDK binary — distributed only through their auth-walled support portal — into its own repo, which raises a redistribution question and forces consumers to carry a binary they cannot audit or update. The Intent API needs no dependency at all, so this installs and autolinks like any other package.
Hardware-verified on a Honeywell CK65 (Android 8.1, Data Collection Service 1.95.00.0039): claim, release, decoder configuration, check digit transmission, trigger disable and barcode delivery all exercised on the device. Every property key is checked against the string table of the device's own
DataCollectionService.apkrather than inferred — seeClaimRequest.ktfor the command to re-verify against other firmware.
Install
npm install @karus-systems/react-native-honeywell-intentAndroid only. Autolinks on React Native 0.60+.
Usage
import { useHoneywellScanner } from '@karus-systems/react-native-honeywell-intent';
function ScanScreen() {
const { scannerState, isScannerReady, diagnostics } = useHoneywellScanner({
onBarcode: (event) => console.log(event.data, event.labelType),
});
return <Text>{scannerState}</Text>;
}The hook claims the scanner on mount, re-claims it when the app returns to the foreground, and releases it on unmount.
Scanner lifecycle
The reader is in one of three states. The counterintuitive one is Released: handing the scanner back does not switch it off, it gives it to the device default — so the beam still fires and decoded text can still land in whatever field has focus.
stateDiagram-v2
[*] --> Released
Released : Released
Released : Device default (Scan Wedge) owns the reader
Released : Beam FIRES · your app receives nothing
Scanning : Scanning
Scanning : Claim held · trigger live
Scanning : Beam fires · barcodes delivered to your app
Stopped : Stopped
Stopped : Claim held · trigger disabled
Stopped : Beam does NOT fire · nothing scans, anywhere
Released --> Scanning : startReading()
Scanning --> Stopped : stopReading()
Stopped --> Scanning : startReading()
Scanning --> Released : releaseReader()
Stopped --> Released : releaseReader()
Scanning --> Released : app backgrounds
Stopped --> Released : app backgrounds
Released --> Scanning : app resumes (was scanning)
Released --> Stopped : app resumes (was stopped)Backgrounding always releases, and resuming restores the state you were in — including a disabled trigger.
That release matters because the Data Collection Service does not revoke a claim when the holding app backgrounds. Holding it is harmless on its own — with a claim held from the background, Honeywell's own ScanDemo scanned normally in the foreground and no barcode leaked to the backgrounded app, so the service does arbitrate by foreground. What it will not do is undo a property. A trigger disabled by an app that then backgrounds stays disabled for the whole device until that app returns, which is why the library releases on pause rather than trusting the service to clean up after it.
Readiness is evidence, not assumption
A claim is never acknowledged. The Intent API has no reply, no status action and no queryable service, and the Data Collection Service silently transfers the reader when another app claims it. So "claimed" is what the library asked for, and a delivered barcode is the only proof it worked.
stateDiagram-v2
[*] --> Unconfirmed : claim sent
Unconfirmed : Unconfirmed
Unconfirmed : We asked for the reader · nothing has proved it
Confirmed : Confirmed
Confirmed : A barcode arrived · the claim was real
Unconfirmed --> Confirmed : barcode delivered
Confirmed --> Unconfirmed : new claim supersedes the proofisScanConfirmed from the hook (and diagnostics.scanConfirmed) exposes this,
so a UI can say "claimed, unconfirmed" rather than asserting a readiness nobody
has verified. Both are computed locally — getDiagnostics() performs no
round-trip and cannot block.
Stopping the scanner
Three different things are easy to confuse, and only one of them actually stops the reader:
| Call | Effect |
| --- | --- |
| stopReading() | Disables the trigger, keeps the claim. The scanner genuinely stops. |
| releaseReader() | Hands the reader back. The beam fires again under the device default. |
| backgrounding | Releases automatically, re-claims on resume. Handled for you. |
releaseReader() is not a way to stop scanning. A released reader reverts to the
device default — typically Scan Wedge — so the trigger still fires and decoded
text can land in whatever field has focus. Use stopReading() for a screen where
a stray scan would be destructive, and releaseReader() only when you are done
with the scanner entirely.
Configuration
Expo
Add the config plugin. It writes the Android resources at prebuild and adds the
<queries> entries diagnostics needs on Android 11+.
{
"plugins": [
[
"@karus-systems/react-native-honeywell-intent",
{
"scanAction": "com.yourapp.SCAN",
"scanner": "dcs.scanner.imager",
"profile": "DEFAULT",
"decoders": ["code128", "qrcode", "ean13"]
}
]
]
}Bare React Native
Write the same values yourself in android/app/src/main/res/values/honeywell_intent.xml
— see the bare example.
All values are optional; omit the file and the library defaults apply.
| Option | Default | Notes |
| --- | --- | --- |
| scanAction | <applicationId>.SCAN | Action decoded barcodes are broadcast to |
| scanner | dcs.scanner.imager | Or dcs.scanner.ring for a Bluetooth ring scanner |
| profile | DEFAULT | Named EZConfig profiles only |
| decoders | ["code128"] | Symbologies to enable; all others are explicitly disabled |
Symbologies
Every symbology below is written on every claim — the ones you list as enabled,
the rest as disabled. That matters when more than one app uses the scanner on a
device: a Honeywell claim inherits whatever the previous claimant configured, so
anything left unwritten would carry one app's settings into the next. Listing
decoders therefore sets the reader's full state rather than adding to it.
| Group | Values |
| --- | --- |
| Linear | code128 gs1-128 code39 code93 code11 codabar msi telepen trioptic tlc39 |
| Retail | ean8 ean13 upca upce upce1 |
| 2 of 5 | i2of5 matrix-25 standard-25 iata-25 hk-25 |
| GS1 DataBar | databar-14 databar-expanded databar-limited composite |
| Stacked | pdf417 micropdf417 codablock-a codablock-f |
| 2D | qrcode datamatrix aztec maxicode dotcode hanxin gridmatrix digimarc dpm |
| Postal | postal korea-post |
Check digits are transmitted for EAN-8/13 and UPC-A/E. Decoder sub-options — supplemental addenda, Code 39 full ASCII, Code 128 ISBT, video reverse — are pinned off and are not currently configurable.
API
| Export | Purpose |
| --- | --- |
| useHoneywellScanner(options) | Hook: claim lifecycle, barcode delivery, diagnostics |
| claimScanner() | Claim the reader and apply the configured decoders |
| releaseScanner() | Release the reader |
| setScannerClaimed(claimed) | Claim or release, whichever matches |
| setTriggerEnabled(enabled) | Make the trigger inert while keeping the claim |
| triggerSoftScan(start) | Start/stop the beam without the hardware trigger |
| getDiagnostics() | Device and claim state snapshot |
| openScannerSettings() | Deep-link to the Data Collection Service's settings |
| addBarcodeListener(fn) | Raw barcode events |
| addClaimStateListener(fn) | Claim taken/lost, including on backgrounding |
Relationship to the Zebra sibling
This library deliberately mirrors
@karus-systems/react-native-zebra-datawedge
so a codebase can support both fleets with one shape. Two differences are real
and worth understanding rather than papering over:
Claim is a session, not a profile. A DataWedge profile is written once and
persists on the device across reboots. A Honeywell claim is held by one app at a
time. That is why the entry point is claimScanner() and not configureProfile()
— calling it once at startup and assuming the device stays configured is the
single most common way to end up with a scanner that mysteriously stops working.
Note the Data Collection Service does not revoke a claim when the holding app
backgrounds — verified on a CK65, where a backgrounded app holding a claim with
its trigger disabled left the reader dead for the entire device. This library
therefore releases on pause itself and re-claims on resume, so a backgrounded app
never keeps the scanner from the rest of the device. addClaimStateListener
reports both transitions.
Diagnostics are thinner. DataWedge is a separate installable app that answers
questions about itself — is it enabled, does the profile exist, what version. The
Data Collection Service is firmware and answers nothing over the intent API. So
Diagnostics here carries device facts and claim state only, all synchronous.
There is no timeout, no "unknown" state, and correspondingly less to reconcile.
Alternatives and when to use them
Honeywell exposes three scanning surfaces, in increasing order of capability:
- Scan Wedge — keyboard wedge. Types decoded text into the focused field. Needs no code at all. If that is genuinely all you need, turn it on in the device settings and do not install this library.
- Data Collection Intent API — what this library wraps. Real barcode events, symbology configuration, soft trigger, claim lifecycle. No dependency.
- Mobility SDK (
com.honeywell.aidc) — fullBarcodeReaderPropertiesaccess and reader introspection, at the cost of a portal-gated binary. If you need properties this library cannot reach, that is the escape hatch — seereact-native-honeywell.
Contributing
See CONTRIBUTING.md.
License
MIT
