Building QR code creation and camera scanning in Android
Implementing QR code generation and scanning gives an Android app two complementary abilities: producing a readable symbol and recovering its payload from a camera. A code can hold a web address, order identifier, contact details, Wi-Fi setup or structured JSON, while a scanner converts the pattern into text that the app can validate and route.
A dependable implementation keeps these paths separate but consistent. Generation is a local rendering task; scanning adds camera permissions, image analysis and result handling. The flow should work on ordinary Android phones, recover gracefully from poor lighting, and explain any permission or network failure without exposing sensitive data.
Designing payloads and screen flow
Begin with a precise payload contract rather than treating every QR code as a URL. Define whether the value is plain text, a custom URI or JSON, and record its maximum length and expected format. The same contract should guide generation, scanning and testing so a malformed payload cannot silently enter the app.
Keep generation and recognition in separate components. A repository can expose createBitmap and parseResult functions, while the interface supplies a Bitmap for sharing or display and a validated result for navigation. This separation makes the scanner easier to test and lets an offline build still generate usable codes when camera services are unavailable.
Creating QR code bitmaps
Add ZXing’s lightweight core module for encoding, then create the bitmap on a background dispatcher because larger payloads and high-resolution output can block the main thread. A compact helper is enough for most apps:
implementation("com.google.zxing:core:3.5.3")
fun qrBitmap(value: String, size: Int): Bitmap {
val matrix = QRCodeWriter().encode(
value,
BarcodeFormat.QR_CODE,
size,
size,
mapOf(EncodeHintType.MARGIN to 1)
)
val pixels = IntArray(size * size)
for (y in 0 until size) {
for (x in 0 until size) {
pixels[y * size + x] =
if (matrix[x, y]) Color.BLACK else Color.WHITE
}
}
return Bitmap.createBitmap(pixels, size, size, Bitmap.Config.ARGB_8888)
}
Import Bitmap, Color, BarcodeFormat, EncodeHintType and QRCodeWriter. The helper requests a one-module quiet zone, which helps scanners detect the symbol, and returns an opaque image ready for an ImageView. Generate enough pixels for the final display size, test with several real phones and avoid interpolation that softens module edges.
Choose high-contrast colours and keep branding away from the code’s finder patterns. Anyone can decode the contents, so invoices, access tokens or personal records require suitable encryption or signing. Validate the maximum payload length before encoding because dense codes become difficult to scan.
Building the camera scanner
Use CameraX for the preview and on-device barcode recognition with ML Kit. Add com.google.mlkit:barcode-scanning:17.3.0 plus CameraX’s camera-camera2, camera-lifecycle and camera-view modules using compatible stable versions. Add android.permission.CAMERA to the manifest, request it at runtime and bind Preview and ImageAnalysis to the activity lifecycle.
Configure ImageAnalysis with STRATEGY_KEEP_ONLY_LATEST, restrict ML Kit to Barcode.FORMAT_QR_CODE and pass each frame to the scanner:
val mediaImage = imageProxy.image
if (mediaImage == null) {
imageProxy.close()
return
}
scanner.process(
InputImage.fromMediaImage(
mediaImage,
imageProxy.imageInfo.rotationDegrees
)
).addOnSuccessListener { codes ->
codes.firstOrNull()?.rawValue?.let(::onCode)
}.addOnCompleteListener {
imageProxy.close()
}
Guard the result callback so one code cannot trigger navigation repeatedly, then show the decoded text or a short preview. The camera screen also needs a clear framing area, a torch control, a close action and TalkBack announcements. While the scanner model loads, display progress feedback. For an older app being migrated, this legacy spinner styling example shows how to match a Holo-era progress indicator to existing resources.
Validating results and comparing workflows
Both workflows solve different problems, so choose libraries and validation rules for the result your app actually needs.
| Concern | Generation workflow | Scanning workflow |
|---|---|---|
| Purpose | Converts known data into a symbol | Recovers data from camera frames |
| Main input | String, URI or structured payload | Image pixels and a detected barcode |
| Main output | Bitmap, image file or share intent | Raw text or structured result |
| Typical tooling | ZXing core | CameraX with ML Kit or ZXing |
| Connectivity | Usually works offline | Recognition works offline, but follow-up actions may need data |
| Main risk | Poor contrast, clipping or excessive density | Permission errors, duplicate results or malicious links |
A scanner can recognise text offline, but that does not prove the destination is safe. Parse structured values, check required fields and confirm the scheme and host before opening a link. Show the decoded domain and require confirmation for unfamiliar addresses, reducing the risk that a modified code sends a customer to a phishing page.
Handling failures, access, and offline use
Treat permission denial, unavailable cameras and rotation as normal states rather than crashes. Offer a manual code-entry fallback where it makes sense, retain the entered value after validation errors and rotate the camera preview with the display. Use an orientation lock only if it matches the scan task, since accessibility guidance favours adaptable layouts.
Australian deployments often combine city and regional use, so design for both. A market organiser might issue codes for stalls at Sydney Farmers Markets, Queen Victoria Market in Melbourne and Brisbane’s Riverside Markets. Encoded receipts may show Australian dollars and the 10% GST, while event schedules must account for Brisbane staying on UTC+10, Sydney and Melbourne moving to UTC+11 during daylight saving, and Adelaide using UTC+9:30. Offline capture supports areas with unreliable mobile coverage, while personal information requires appropriate retention controls and, where applicable, alignment with the Australian Privacy Principles.
Testing and releasing for Australian users
Test generation with short and long payloads, unusual characters and every planned size. Instrument scanner flows with a mocked analyser, then repeat them on physical devices under bright sunlight, dim indoor light and oblique angles.
Apply these checks to generated output:
- The code has a clear quiet zone and remains readable at its smallest display size.
- Colour contrast stays strong in both light and dark application themes.
- Exported files preserve square pixels and sufficient resolution.
- Payloads match the agreed schema and length limit.
Scanner tests should cover granting and denying camera access, cancelling a scan and receiving malformed content. Check that analysis stops when the screen closes and that the first successful result is handled exactly once.
Apply these checks to camera recognition:
- The preview rotates correctly and remains aligned with the scanning frame.
- Torch, cancel and retry controls work with large text enabled.
- Recognition works offline and respects the device’s battery settings.
- Unknown codes receive a clear validation message without opening a link.
Finally, verify TalkBack labels, colour contrast and touch targets before publishing an APK or Android App Bundle tested against Play Store requirements. Store only the minimum payload data needed for the workflow, document retention in the privacy policy and use staged rollouts so regressions can be stopped before they spread across Australia.