Capacitor plugins in production: barcode, torch, and contacts
Lessons from shipping yagnik-scanner, yagnik-flashlight, and yagnik-contacts on Ionic field apps—permissions, OEM quirks, and npm maintenance.
Field apps fail in the warehouse, not in the demo. The scanner does not read a wrinkled label in dim light; Android 13 permission dialogs block the first run; iOS kills the WebView if the native plugin throws on the wrong thread. I shipped Ionic + Capacitor logistics apps and later published Capacitor plugins (yagnik-scanner, yagnik-flashlight, yagnik-contacts) because the gap between "works on my phone" and "works on a ₹8k device in a loading bay" is where production lives.
This post is the maintenance and integration checklist I wish I had before the first OEM escalation.
Why wrap native instead of only using community plugins
Capacitor Community barcode scanner is solid until you need:
- Torch control during scan on devices where the camera preview and torch APIs fight.
- Contacts read with a stable promise API across Capacitor 3→6 migrations in legacy apps.
- A single maintainer who can patch OEM-specific failures without waiting on a large committee.
Publishing small focused plugins trades governance overhead for control. Each plugin should do one job and document what it does not do.
Permissions are part of the UX flow
Never call requestPermissions() on app boot. Users deny and remember.
Pattern that works:
- Explain on a screen why scan is needed (receive goods, verify party).
- Trigger permission on first tap of "Scan".
- If denied, show settings deep link with copy in the operator's language—not a raw error code.
Android 12+ needs separate rationales for camera vs nearby devices on some builds. iOS needs NSCameraUsageDescription strings that match the screen copy, not generic "This app needs camera."
Barcode: preview, torch, and failure modes
yagnik-scanner wraps scan flows with explicit start/stop so the camera releases when modals close. Leaving the preview running drains battery and blocks other apps on low-RAM phones.
Common failures:
| Symptom | Likely cause |
|---|---|
| Never resolves | WebView paused, plugin called after unmount |
| Reads once then hangs | Scan session not stopped on route change |
| Blurry at distance | Fixed focus not supported; add manual entry fallback |
Always ship manual entry beside scan. Operators trust keyboards when labels are damaged.
Flashlight plugin scope
Torch for scan assist sounds trivial until Samsung builds behave differently from Pixel. Keep the API minimal: enable(), disable(), isAvailable(). Do not promise strobe or brightness levels you cannot test across OEMs.
Contacts: privacy and partial data
Reading contacts for "pick a party phone" triggers Play Store data safety forms and user suspicion. Load contacts only when the user opens the picker; never sync the whole address book to your server.
Return normalized shapes { name, phone } and dedupe in the app layer. Document that iOS may return limited fields without full access.
npm publishing and versioning
- Semantic versioning per plugin; apps pin ranges in
package.json. - Changelog entry per fix—even patch—for field teams searching "scanner 1.2.3 crash".
- Peer dependency on
@capacitor/corerange tested in CI. - README with Android/iOS setup steps copy-pasteable; missing Gradle config is the #1 issue report.
Testing without a device farm
You will not own every OEM. Still:
- Test on one low-end Android (2–3 GB RAM) and one iOS device per release.
- Exercise cold start → scan → background → resume.
- Run Capacitor
syncafter plugin bumps in CI so drift is caught early.
Integration in Ionic Angular apps
Wrap plugins behind app services (BarcodeService, TorchService) so pages do not import npm paths directly. Mock services in unit tests; e2e on real devices for one golden path.
Offline matters: scan success should write to local queue if POST fails; show pending state on the row. Field users will tap again if the UI lies about sync.
Open source as credibility
Public plugins are proof you understand native bridges—not resume fluff. Link them from the studio site with one-line notes ("barcode scan for Capacitor 6"). Prospects doing mobile work read npm the way backend hires read GitHub.
Closing
Production Capacitor work is permission timing, session lifecycle, and honest fallbacks. Plugins should be small, documented, and boring to integrate—excitement belongs in the operator workflow, not in the crash logs.