Build identity scanning into Android.
Integrate AAMVA PDF417 and ICAO MRZ scanning with Kotlin, CameraX, ML Kit, and a still-image ZXing fallback.
Overview
DLens processes camera frames and document payloads on device. Driver licences use PDF417; passports, national IDs, and residence documents use ICAO TD1, TD2, or TD3 machine-readable zones. MRZ results must pass check-digit validation.
Requirements
- Android API 26+ and compile SDK 35+
- Java 17 and AndroidX
- A physical camera device for live scanning
- Credentials issued for the exact Android
applicationId
Installation
Unpack the distribution beside your app and include the library module.
include(":dlens-sdk")
project(":dlens-sdk").projectDir =
file("../DLensAndroid/dlens-sdk")dependencies {
implementation(project(":dlens-sdk"))
}The library manifest contributes camera permission and the private scanner activity.
Initialise
DLens.initialize(
context = applicationContext,
apiKey = BuildConfig.DLENS_API_KEY,
secret = BuildConfig.DLENS_SECRET,
configuration = DLensConfiguration(
scanMode = ScanMode.ALL,
timeoutSeconds = 60,
),
)Present the supplied scanner
DLensScannerActivity requests permission, manages the camera, and returns result JSON.
private val scan = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { response ->
when (response.resultCode) {
Activity.RESULT_OK -> {
val json = response.data?.getStringExtra(
DLensScannerActivity.EXTRA_RESULT
)
}
Activity.RESULT_CANCELED -> Unit
else -> {
val code = response.data?.getStringExtra(
DLensScannerActivity.EXTRA_ERROR_CODE
)
}
}
}
scan.launch(
Intent(this, DLensScannerActivity::class.java)
.putExtra(DLensScannerActivity.EXTRA_MODE, ScanMode.ALL.name)
)Result dates are ISO yyyy-MM-dd strings.
Embed a custom preview
Request camera permission, then attach DLensScanner to a CameraX PreviewView.
val scanner = DLensScanner(
context = this,
lifecycleOwner = this,
previewView = binding.preview,
mode = ScanMode.ALL,
)
scanner.start(
onResult = ::showResult,
onError = { showError(it.message.orEmpty()) },
)
scanner.setTorch(true)
scanner.setZoom(1.5f)
// Release in onDestroy:
scanner.stop()Create a new scanner for each session. stop() releases its analysis executor.
Scan an existing image
DLens.scan(bitmap, mode = ScanMode.ALL) { outcome ->
outcome.onSuccess(::showResult)
outcome.onFailure { showError(it.message.orEmpty()) }
}Parse a payload
val result = DLens.parse(rawAamvaOrMrzText)Configuration
| Property | Default | Purpose |
|---|---|---|
| scanMode | BARCODE_ONLY | Barcode, MRZ, or both |
| timeoutSeconds | 60 | Zero disables timeout |
| autoZoom | true | Automatic zoom policy |
| minZoom / maxZoom | 1.0 / 2.5 | Allowed zoom range |
| rescanZoomOffset | 0.5 | Adjustment for another pass |
| dismissBehavior | AUTOMATIC | Automatic or manual dismissal |
Visibility preferences are also available for cancel, torch, zoom, tips, instructions, and the mode badge. Use DLens.configure(...) after initialization.
Result model
Required: first name, last name, licence/document number, and raw payload. Optional fields cover other names, dates, licence details, address, country, gender, eye colour, and height. fullName joins present name components.
Errors
Catch DLensException: NotInitialized, InvalidApiKey, InvalidSecret, LicenseExpired, LicenseKeyMismatch, CameraPermissionDenied, Timeout, and ScanningFailed.
Credentials and validation
Keep production credentials out of source control and inject them through protected build configuration.
Camera-level accuracy must be benchmarked on representative physical devices against the same document corpus used for the iOS release.