DEV Community

Getinfo Toyou
Getinfo Toyou

Posted on

Building an Offline QR Code Scanner for Android: The Hard Way vs. The Right Way

Why I Almost Gave Up Halfway Through

I've been building small utility apps for Android for a while now, and when I decided to build a QR code scanner, I thought it would be straightforward. It wasn't. But the struggle taught me more about mobile barcode processing than I expected — and the end result is Sharp QR, a reliable offline QR scanner and generator that actually works when you need it.

Let me walk you through what I learned, what I got wrong first, and why some of those wrong turns were actually instructive.


The Hard Way: Rolling Your Own Barcode Detection

My first instinct was to write a custom image processing pipeline. I wanted to understand the problem from the ground up. Here's what that looked like:

  • Manual camera frame capture using Camera2 API
  • Converting YUV buffers to grayscale bitmaps by hand
  • Applying threshold filters to improve contrast on faded codes
  • Writing finder pattern detection logic from scratch

The result? It kind of worked on high-contrast codes printed on white paper. Try it on a faded label on a cardboard box at a store, and it would just stare blankly back at you.

The performance was also rough. Processing frames on the main thread caused visible lag. Moving it to a background thread introduced synchronization bugs. I was spending weeks on infrastructure that had nothing to do with the user experience I actually wanted to deliver.


The Right Way: Standing on Solid Shoulders

I eventually stopped fighting the problem and reached for ML Kit's barcode scanning API. Combined with ZXing for QR code generation, this became the real foundation of Sharp QR.

Tech stack breakdown:

  • Language: Kotlin
  • Camera: CameraX (lifecycle-aware, much cleaner than Camera2 for this use case)
  • Scanning: ML Kit Barcode Scanning (on-device, works fully offline)
  • Generation: ZXing core library
  • UI: Material Design 3 components
  • Architecture: MVVM with ViewBinding

Switching to ML Kit didn't feel like giving up — it felt like making a smart engineering decision. The model runs entirely on-device. No network calls, no latency spikes, no privacy concerns about sending images to a server. That last point matters more than people realize when you're scanning things like Wi-Fi passwords or contact cards.


The Actual Technical Challenges

Even with the right libraries, there were real problems to solve:

1. Faded code recovery
ML Kit handles this better than my homebrew attempt, but I still had to tune the camera preview resolution and frame analysis rate. Too high a resolution and the analysis queue backs up. Too low and you miss detail on worn codes. CameraX's ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST was the key — it drops frames rather than queuing them.

2. Offline-first architecture
I wanted zero network dependency at runtime. ML Kit's bundled model option solved scanning. For QR generation (Wi-Fi credentials, vCards, URLs), everything is computed locally using ZXing's QRCodeWriter. The app literally works in airplane mode.

3. Format breadth
Users don't only scan QR codes. They scan Code 128 barcodes on grocery items, EAN-13 on retail products, Data Matrix codes on electronics. Supporting all of these without the UI becoming a format-picker maze required some UX thinking alongside the technical work.


Lessons Learned

  • Premature optimization of the wrong layer is a real trap. I spent days on image processing that a well-maintained library handled in hours of integration.
  • On-device ML is genuinely viable now. A few years ago, offline-capable ML on mobile felt like a tradeoff. Today, ML Kit's bundled models are fast and accurate enough that cloud processing feels unnecessary for this use case.
  • CameraX made lifecycle management actually manageable. If you're building anything camera-related on Android and you're still on Camera2 directly, reconsider.
  • Utility apps live or die on reliability. Users will forgive a plain UI. They won't forgive a scanner that fails on three codes in a row.

Try It

If you're on Android and want a no-fuss barcode tool that works without internet, give Sharp QR a try. It handles scanning and generation across all common formats, works offline, and doesn't ask for unnecessary permissions.

Building it was humbling in the right ways. The hard path clarified what the right path actually was — and the right path still had plenty of real engineering in it.

Happy to answer questions about any part of the stack in the comments.

Top comments (0)