Complete Manual for document detection in Android applications
Estimated time: 50-70 minutes for complete configuration
What will you learn from this manual?
The manual will teach you how to implement and use the Detector document SDK of JAAK for automated capture of identity documents in native Android applications. No advanced technical skills required - Just follow the steps.
Before Start - Checks list
Be sure to have these elements ready:
- Android Studio Iguana installed
- Android device with API 26 + (Android 8.0 +)
- At least 2GB free RAM (for ML processing)
- Access to Maren Repository JAAK
- Basic knowledge of Kotlin / Java
- Test ID
Contents Index
| Section | What will you do? | Time |
|---|---|---|
| Step 1 | Configure Project and Units | 15 min |
| Step 2 | Basic implementation of SDK | 20 min |
| Step 3 | Configure permissions and components | 10 min |
| Step 4 | Management of responses and 4 images | 20 min |
| Step 5 | Test document detection | 10 min |
Step 1: Configure Project and Units
Objective
Configure the development environment and add the necessary SDK.
Technical requirements
| Requirement | Version | Mandatory? |
|---|---|---|
| Android Studio | Iguana | Yes |
| Fat | 8.4+ | Yes |
| minSdk | 26 | Yes |
| targetSdkVersion | 33 | Yes |
| Kotlin | 1.9.22+ | Yes |
| Free RAM | 2GB + | Yes (for ML) |
1.1 Building (Project) configuration
buildscript {
ext.kotlin_version = "1.9.22"
ext.hilt_version = '2.46'
repositories {
google()
mavenCentral()
maven {
url 'https://us-maven.pkg.dev/jaak-platform/jaak-android'
}
maven {
url 'https://jitpack.io'
}
}
dependencies {
classpath 'com.android.tools.build:gradle:8.3.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
classpath 'com.google.gms:google-services:4.4.2'
}
}
1.2 Settings.gradle configuration
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url 'https://jitpack.io' }
maven {
url 'https://us-maven.pkg.dev/jaak-platform/jaak-android'
}
}
}
1.3 Build.gradle configuration (Module app)
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}
android {
compileSdk 35
defaultConfig {
minSdk 26
targetSdk 33
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_18
targetCompatibility JavaVersion.VERSION_18
}
buildFeatures {
viewBinding = true
}
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version")
implementation("androidx.core:core-ktx:1.15.0")
implementation("androidx.appcompat:appcompat:1.7.0")
implementation("androidx.constraintlayout:constraintlayout:2.2.0")
implementation("com.google.dagger:hilt-android:$hilt_version")
kapt("com.google.dagger:hilt-android-compiler:$hilt_version")
// Document Detector SDK
implementation("com.jaak.documentdetectorsdk:jaakdocumentdetector-sdk:3.0.0")
// TensorFlow Lite Support
implementation 'org.tensorflow:tensorflow-lite-support:0.4.4'
}
Step 2: Basic implementation of SDK
Objective
Create the basic implementation of the Detector document SDK with automatic detection.
2.1 Create Application Class
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class DocumentDetectorApp : Application()
2.2 Main activity
import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.jaak.documentdetectorsdk.sdk.DocumentDetectorSDK
import com.jaak.documentdetectorsdk.ui.adapter.DocumentDetectorListener
import com.jaak.documentdetectorsdk.utils.Utils
import com.example.databinding.ActivityMainBinding
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class MainActivity : AppCompatActivity(), DocumentDetectorListener {
private lateinit var binding: ActivityMainBinding
private lateinit var documentDetectorSDK: DocumentDetectorSDK
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setupDocumentDetector()
setupButtons()
}
private fun setupDocumentDetector() {
documentDetectorSDK = DocumentDetectorSDK(this, this)
}
private fun setupButtons() {
binding.btnStartDetection.setOnClickListener {
documentDetectorSDK.startDocumentDetector(1)
}
}
override fun onSuccessDocumentDetector(
typeProcess: Int,
frontOriginalUri: Uri?,
frontCropUri: Uri?,
backOriginalUri: Uri?,
backCropUri: Uri?
) {
handleSuccess(typeProcess, frontOriginalUri, frontCropUri, backOriginalUri, backCropUri)
}
override fun onErrorDocumentDetector(text: String) {
handleError(text)
}
private fun handleSuccess(
typeProcess: Int,
frontOriginal: Uri?,
frontCrop: Uri?,
backOriginal: Uri?,
backCrop: Uri?
) {
val message = when (typeProcess) {
1 -> "document captured from camera"
else -> "Completion"
}
binding.tvStatus.text = message
// Procure 4 images if available
frontOriginal?.let { uri ->
val base64 = Utils.uriToBase64(contentResolver, uri)
processImage(base64, "front_original")
}
frontCrop?.let { uri ->
val base64 = Utils.uriToBase64(contentResolver, uri)
processImage(base64, "front_crop")
}
backOriginal?.let { uri ->
val base64 = Utils.uriToBase64(contentResolver, uri)
processImage(base64, "back_original")
}
backCrop?.let { uri ->
val base64 = Utils.uriToBase64(contentResolver, uri)
processImage(base64, "back_crop")
}
}
private fun handleError(text: String) {
binding.tvStatus.text = "Error: $text"
}
private fun processImage(base64: String?, type: String) {
base64?.let {
android.util.Log.d("DocumentDetector", "$type: ${it.length} caracteres")
}
}
}
2.3 Layout XML (activity _ main.xml)
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="20dp">
<TextView
android:id="@+id/tvTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="JAAK Document Detector"
android:textSize="24sp"
android:textStyle="bold"
android:textAlignment="center"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="20dp" />
<TextView
android:id="@+id/tvStatus"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Press the button to start"
android:textAlignment="center"
android:padding="15dp"
android:background="@drawable/rounded_background"
app:layout_constraintTop_toBottomOf="@id/tvTitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="30dp" />
<Button
android:id="@+id/btnStartDetection"
android:layout_width="280dp"
android:layout_height="60dp"
android:text="Start Detection"
android:textSize="16sp"
app:layout_constraintTop_toBottomOf="@id/tvStatus"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginTop="40dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
Step 3: Configure Permissions and Components
Objective
Configure necessary permissions and system components correctly.
3.1 AndroidManifes.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- Permisos necesarios -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:replace="android:maxSdkVersion"
android:maxSdkVersion="28" />
<application
android:name=".DocumentDetectorApp"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat.Light.DarkActionBar"
tools:replace="android:theme,android:name">
<activity
android:name=".MainActivity"
android:exported="true"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
3.2 Performance of Permises at Runtime
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
class MainActivity : AppCompatActivity(), DocumentDetectorListener {
companion object {
private const val PERMISSIONS_REQUEST_CODE = 100
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
checkAndRequestPermissions()
setupDocumentDetector()
setupButtons()
}
private fun checkAndRequestPermissions() {
val permissionsNeeded = mutableListOf<String>()
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
permissionsNeeded.add(Manifest.permission.CAMERA)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES)
!= PackageManager.PERMISSION_GRANTED) {
permissionsNeeded.add(Manifest.permission.READ_MEDIA_IMAGES)
}
} else {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
permissionsNeeded.add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
}
if (permissionsNeeded.isNotEmpty()) {
ActivityCompat.requestPermissions(
this,
permissionsNeeded.toTypedArray(),
PERMISSIONS_REQUEST_CODE
)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
PERMISSIONS_REQUEST_CODE -> {
if (grantResults.isNotEmpty() &&
grantResults.all { it == PackageManager.PERMISSION_GRANTED }) {
binding.tvStatus.text = "Permisos concedidos"
} else {
binding.tvStatus.text = "Permisos denegados"
}
}
}
}
}
Track 4: Reg Management and 4 Images System
Objective
Implement complete management of the new 4-image system SDK v3.0.0.
4.1 Full 4 Images processing
override fun onSuccessDocumentDetector(
typeProcess: Int,
frontOriginalUri: Uri?,
frontCropUri: Uri?,
backOriginalUri: Uri?,
backCropUri: Uri?
) {
try {
val documentImages = DocumentImages()
// Procure original front image
frontOriginalUri?.let { uri ->
documentImages.frontOriginal = Utils.uriToBase64(contentResolver, uri)
android.util.Log.d("DocumentDetector", "Front Original: ${documentImages.frontOriginal?.length} chars")
}
// Procure cut front image
frontCropUri?.let { uri ->
documentImages.frontCrop = Utils.uriToBase64(contentResolver, uri)
android.util.Log.d("DocumentDetector", "Front Crop: ${documentImages.frontCrop?.length} chars")
}
// Reprocessing original back image (if available)
backOriginalUri?.let { uri ->
documentImages.backOriginal = Utils.uriToBase64(contentResolver, uri)
android.util.Log.d("DocumentDetector", "Back Original: ${documentImages.backOriginal?.length} chars")
}
// Treat cut back image (if available)
backCropUri?.let { uri ->
documentImages.backCrop = Utils.uriToBase64(contentResolver, uri)
android.util.Log.d("DocumentDetector", "Back Crop: ${documentImages.backCrop?.length} chars")
}
// Determine document type
val documentType = if (backOriginalUri != null || backCropUri != null) {
"double_sided"
} else {
"single_sided"
}
// To process complete document
processCompleteDocument(documentImages, documentType, typeProcess)
binding.tvStatus.text = "document successfully processed"
} catch (e: Exception) {
android.util.Log.e("DocumentDetector", "Error processing images: ${.message}")
binding.tvStatus.text = "Error processing images"
}
}
data class DocumentImages(
var frontOriginal: String? = null,
var frontCrop: String? = null,
var backOriginal: String? = null,
var backCrop: String? = null
)
private fun processCompleteDocument(
images: DocumentImages,
documentType: String,
typeProcess: Int
) {
android.util.Log.d("DocumentDetector", "_")
android.util.Log.d("DocumentDetector", "Tipo: $documentType")
android.util.Log.d("DocumentDetector", "Process: $typeProcess")
// Validate that we have at least the front image
if (images.frontOriginal.isNullOrEmpty() && images.frontCrop.isNullOrEmpty()) {
throw IllegalStateException("No valid front images found")
}
// Create payload for backend
val payload = createDocumentPayload(images, documentType)
// Send to backend or process locally
sendToBackend(payload)
android.util.Log.d("DocumentDetector", "Reprocessing document")
}
private fun createDocumentPayload(images: DocumentImages, documentType: String): Map<String, Any> {
return mapOf(
"documentType" to documentType,
"timestamp" to System.currentTimeMillis(),
"version" to "3.0.0",
"images" to mapOf(
"frontOriginal" to (images.frontOriginal ?: ""),
"frontCrop" to (images.frontCrop ?: ""),
"backOriginal" to (images.backOriginal ?: ""),
"backCrop" to (images.backCrop ?: "")
),
"metadata" to mapOf(
"hasBack" to !images.backOriginal.isNullOrEmpty(),
"hasCrop" to !images.frontCrop.isNullOrEmpty(),
"platform" to "android"
)
)
}
private fun sendToBackend(payload: Map<String, Any>) {
android.util.Log.d("DocumentDetector", "Enviando a backend: ${payload.keys}")
// Implement call to your API
}
4.2 Advanced Errors Management
override fun onErrorDocumentDetector(text: String) {
android.util.Log.e("DocumentDetector", "Error: $text")
when {
text.contains("TensorFlow", ignoreCase = true) -> {
handleMLError(text)
}
text.contains("camera", ignoreCase = true) -> {
handleCameraError(text)
}
text.contains("permission", ignoreCase = true) -> {
handlePermissionError(text)
}
text.contains("memory", ignoreCase = true) -> {
handleMemoryError(text)
}
else -> {
handleGenericError(text)
}
}
}
private fun handleMLError(error: String) {
binding.tvStatus.text = "ML processing error"
showErrorDialog(
"Processing Error",
"The artificial intelligence model could not be loaded.\n\nVerify that the device has enough free memory."
)
}
private fun handleCameraError(error: String) {
binding.tvStatus.text = "Camera error"
showErrorDialog(
"Camera error",
"The camera could not be accessed.\n\nVerify the app's permissions."
)
}
private fun handlePermissionError(error: String) {
binding.tvStatus.text = "Permisos denegados"
showErrorDialog(
"Permisos Requeridos",
"The application requires camera and storage permits to work properly."
)
}
private fun handleMemoryError(error: String) {
binding.tvStatus.text = "Memoria insuficiente"
showErrorDialog(
"Memoria Insuficiente",
"The device does not have enough free memory.\n\nClose other applications and try again."
)
}
private fun handleGenericError(error: String) {
binding.tvStatus.text = "Error: $error"
}
private fun showErrorDialog(title: String, message: String) {
androidx.appcompat.app.AlertDialog.Builder(this)
.setTitle(title)
.setMessage(message)
.setPositiveButton("Entendido") { dialog, _ ->
dialog.dismiss()
}
.show()
}
Step 5: Test document detection
Objective
Check that all functionalities of SDK They work properly.
checklist
| Element | State | Description |
|---|---|---|
| Automatic detection | ☐ | AI detects document automatically |
| System 4 images | ☐ | Original and face / back crop |
| Basal conversion64 | ☐ | All images become |
| Overview 2x2 | ☐ | Sample 4 images in grid |
| Double face documents | ☐ | Detects and processes both sides |
Test process
Step A: Test Simple Document
- Press "Start Detection"
- Position document within the framework
- To expect automatic detection (Green frame)
- Check capture automatic
- Review preview on-screen images
Step B: Test Double face document
- Capture front document
- Following instructions To turn
- Capture Reverse automatically
- Check 4 images at preview 2x2
- Confirm processing success
Step C: Check ML quality
- Test with bad light - you have to set up
- Test with tipped document - you have to detect and cut
- Test with different types Documents
- Check logs from TensFlow Lite
Debugging and Troublehooting
Check Device Resources
private fun checkDeviceCapability(): Boolean {
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val memoryInfo = ActivityManager.MemoryInfo()
activityManager.getMemoryInfo(memoryInfo)
val availableMemoryMB = memoryInfo.availMem / (1024 * 1024)
android.util.Log.d("DocumentDetector", "Memoria disponible: ${availableMemoryMB}MB")
return availableMemoryMB > 500
}
private fun enableDebugLogs() {
android.util.Log.d("DocumentDetector", "=== DEBUG DOCUMENT DETECTOR ===")
android.util.Log.d("DocumentDetector", "SDK version: 3.0.0")
android.util.Log.d("DocumentDetector", "Memoria disponible: ${checkDeviceCapability()}")
android.util.Log.d("DocumentDetector", "TensorFlow Lite disponible: ${checkTensorFlowLite()}")
}
private fun checkTensorFlowLite(): Boolean {
return try {
Class.forName("org.tensorflow.lite.Interpreter")
true
} catch (e: ClassNotFoundException) {
false
}
}
Common problems
| Problem | Resolution |
|---|---|
| "TensFlow model error" | Check free memory and reset app |
| "Non-available camera" | Check permits and that are not in use |
| "Launcher isn't registered" | Call Setup at onCreate (), not immediately |
| "Processing failed" | Check RAM available and close other apps |
Full reference
Methods SDK
| Method | Description | Example |
|---|---|---|
| DocumentDetectorSDK (context, list) | Building SDK | DocumentDetectorSDK(this, this) |
| startDocumentDetector (Int) | Begins detection | documentDetectorSDK.startDocumentDetector(1) |
Startable Parameters DocumentDetector
| Mode | Description | When to use |
|---|---|---|
| 1 | Direct camera detection | Immediate document collection |
4 Images System
| Image | Description | When it's generated |
|---|---|---|
| front | Full face image | Always |
| frontCrop | Front cut by AI | When detecting document |
| backOriginal | Full back image | Only if document requires back |
| backCrop | Reverse cut by AI | When you get back |
Fits
| Method | Description | Example |
|---|---|---|
| Utils.uriToBase64 () | Convert URI to Base64 | Utils.uriToBase64(contentResolver, uri) |
Problem solving
Problem: "memory error"
// Check before initializing
private fun initializeSDKSafely() {
if (checkDeviceCapability()) {
setupDocumentDetector()
} else {
showMemoryWarning()
}
}
private fun showMemoryWarning() {
binding.tvStatus.text = "Insufficient memory device"
}
Problem: "Documents aren't detectable"
// Check compatibility TensFlow Lite
private fun checkMLCompatibility() {
try {
val interpreter = org.tensorflow.lite.Interpreter(ByteBuffer.allocate(0))
android.util.Log.d("DocumentDetector", "Compatible")
} catch (e: Exception) {
android.util.Log.e("DocumentDetector", "TF Lite no compatible: ${e.message}")
}
}
Problem: "Preview doesn't display 4 images"
The SDK v3.0.0 automatically includes a preview with Layout 2x2 showing:
- Left upper: Original Front
- Right upper: Front Crop
- Lower left: Original Back
- lower right: Back Crop
No additional configuration required.
Do you need some help?
Support information
- Problem description: What document are you trying to capture vs. what's going on?
- Android Studio Logs: Mistakes Screenshots
- Unit information: RAM, CPU, Android version
- Building Setup Unit versions
- Type of document: INE, passport, license, etc.
Support Contact
Email: support Always include: Log ML, configuration, version SDK 3.0.0
You have successfully implemented the Detector document SDK v3.0.0 JAAK Your app can now detect and process document automatically using artificial intelligence, generating 4 optimization images for processes KYC.