A complete manual for document capture in Android applications
Estimated time: 60-75 minutes for complete configuration
What will you learn from this manual?
This manual will teach you how to implement and use JAAKStamps SDK for automated capture of identity documents in native Android applications with artificial intelligence. 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 6.0 + device (API 23+)
- Gratle 8.4 configured
- Basic knowledge about Kotlin / Android
- Access to a functional camera
- Identification document (INE, IFE, Passport...) for testing
Contents Index
| Section | What will you do? | Time |
|---|---|---|
| Step 1 | Configure Project and Units | 15 min |
| Step 2 | Basic implementation of SDK | 25 min |
| Step 3 | Configure permits and manifests | 10 min |
| Step 4 | Management of responses and images | 15 min |
| Step 5 | Test document capture | 10 min |
Step 1: Configure Project and Units
Objective
Configure Android development environment and add necessary dependencies to SDK.
Technical requirements
| Requirement | Version | Mandatory? |
|---|---|---|
| Android Studio | Iguana | Yes |
| minSdk | 23 (Android 6.0 +) | Yes |
| targetSdkVersion | 33 | Yes |
| compileSandk | 35 | Yes |
| Fat | 8.4 | Yes |
| Java / Kotlin | 17 | Yes |
1.1 Building (Project) configuration
buildscript {
ext.kotlin_version = "1.9.22"
ext.hilt_version = '2.48'
repositories {
google()
mavenCentral()
maven {
url 'https://us-maven.pkg.dev/jaak-platform/jaak-android'
}
}
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"
}
}
1.2 Building Setup (Module: app)
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}
android {
compileSdk 35
defaultConfig {
applicationId "Here."
minSdk 23
targetSdk 33
versionCode 1
versionName "1.0"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '17'
}
buildFeatures {
viewBinding = true
}
}
dependencies {
// Android Core
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.android.material:material:1.12.0'
// Dependency Injection
implementation("com.google.dagger:hilt-android:$hilt_version")
kapt("com.google.dagger:hilt-android-compiler:$hilt_version")
// CameraX
implementation 'androidx.camera:camera-core:1.3.4'
implementation 'androidx.camera:camera-camera2:1.3.4'
implementation 'androidx.camera:camera-lifecycle:1.3.4'
implementation 'androidx.camera:camera-view:1.3.4'
// TensFlow Lite for AI
implementation 'org.tensorflow:tensorflow-lite:2.14.0'
implementation 'org.tensorflow:tensorflow-lite-support:0.4.4'
// ONNX Runtime for classification
implementation 'com.microsoft.onnxruntime:onnxruntime-android:1.16.3'
// JAAK Stamps SDK
implementation("com.jaak.stampssdk:jaakstamps-sdk:1.0.0-beta")
}
Step 2: Basic implementation of SDK
Objective
Create JAAKStamps core implementation SDK with an automated capture.
2.1 Application Class
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class JaakStampsSDKApp : Application()
2.2 Main activity
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import com.jaak.stampssdk.databinding.ActivityMainBinding
import com.jaak.stampssdk.sdk.StampsSDK
import com.jaak.stampssdk.ui.adapter.StampsListener
import com.jaak.stampssdk.utils.Utils
import com.jaak.stampssdk.utils.CameraFacing
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class MainActivity : AppCompatActivity(), StampsListener {
private lateinit var binding: ActivityMainBinding
private lateinit var stampsSDK: StampsSDK
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
installSplashScreen()
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
configureStampsSDK()
binding.btnStartScanner.setOnClickListener {
startDocumentDetection()
}
}
private fun configureStampsSDK() {
stampsSDK = StampsSDK(this, this)
// Recommended configuration
stampsSDK.setAlignmentTolerance(15) // Linkage accuracy (0-100)
stampsSDK.setMaskSize(80) // Detection area (50-100)
stampsSDK.setCropMargin(100) // Filling mark (0-100)
stampsSDK.setCaptureDelay(1500) // Delay before capture (ms)
stampsSDK.setCameraFacing(CameraFacing.BACK) // Rear
stampsSDK.setAutoStampsClassification(true) // Automatic classification with A
// Development tools (optional)
stampsSDK.setShowConfigPanel(false)
stampsSDK.setShowDebugMode(false)
}
private fun startDocumentDetection() {
// The SDK automatically manages camera permissions
stampsSDK.startStamps(1) // Mode 1: Automatic detection with A
}
override fun onSuccessStamps(
typeProcess: Int,
frontOriginalUri: Uri?,
frontCropUri: Uri?,
backOriginalUri: Uri?,
backCropUri: Uri?
) {
when (typeProcess) {
1 -> handleStampsResults(frontOriginalUri, frontCropUri, backOriginalUri, backCropUri)
}
}
private fun handleStampsResults(
frontOriginal: Uri?,
frontCrop: Uri?,
backOriginal: Uri?,
backCrop: Uri?
) {
// Convert URIs to Base64 if necessary
val frontOriginalBase64 = frontOriginal?.let {
Utils.uriToBase64(contentResolver, it) ?: ""
} ?: ""
val frontCropBase64 = frontCrop?.let {
Utils.uriToBase64(contentResolver, it) ?: ""
} ?: ""
Log.d("StampsResult", "Front Original: $frontOriginal")
Log.d("StampsResult", "Front Crop: $frontCrop")
Log.d("StampsResult", "Back Original: $backOriginal")
Log.d("StampsResult", "Back Crop: $backCrop")
Toast.makeText(this, "Documents successfully captured", Toast.LENGTH_SHORT).show()
}
override fun onErrorStamps(text: String) {
Log.e("StampsError", text)
Toast.makeText(this, "Error: $text", Toast.LENGTH_SHORT).show()
}
}
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">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="JAAK Stamps SDK Android"
android:textSize="20sp"
android:textStyle="bold"
android:layout_marginBottom="32dp"
app:layout_constraintBottom_toTopOf="@+id/btnStartScanner"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/btnStartScanner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Scan document"
android:textSize="16sp"
android:padding="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Step 3: Configure Permises and Manifesto
Objective
Configure the permissions and the Android manifesto correctly.
3.1 AndroidManifes.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:replace="android:maxSdkVersion"
android:maxSdkVersion="28" />
<application
android:name="com.jaak.stampssdk.JaakStampsSDKApp"
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="com.jaak.stampssdk.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>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.jaak.stampssdk"
android:exported="false"
android:grantUriPermissions="true"
tools:replace="android:authorities">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>
3.2 file _ paths.xml
Create re / xml / file _ paths.xml:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="Pictures/" />
<external-path name="my_files" path="Android/data/com.jaak.stampssdk/files/Jaak Stamps SDK/" />
</paths>
Track 4: Reactions and Images Management
Objective
Implement complete management of captured images and processing.
4.1 Full Image Processing
private fun handleStampsResults(
frontOriginal: Uri?,
frontCrop: Uri?,
backOriginal: Uri?,
backCrop: Uri?
) {
val documentImages = DocumentImages()
// Full front processing
frontOriginal?.let {
documentImages.frontOriginalBase64 = Utils.uriToBase64(contentResolver, it) ?: ""
documentImages.frontOriginalUri = it
}
// To process cut face
frontCrop?.let {
documentImages.frontCropBase64 = Utils.uriToBase64(contentResolver, it) ?: ""
documentImages.frontCropUri = it
}
// Reg complete (if available)
backOriginal?.let {
documentImages.backOriginalBase64 = Utils.uriToBase64(contentResolver, it) ?: ""
documentImages.backOriginalUri = it
}
// Procure cut back (if available)
backCrop?.let {
documentImages.backCropBase64 = Utils.uriToBase64(contentResolver, it) ?: ""
documentImages.backCropUri = it
}
// Determine document type
val documentType = if (backOriginal != null || backCrop != null) "double_sided" else "single_sided"
// To process complete document
processCompleteDocument(documentImages, documentType)
Log.d("StampsResult", "Processed document: $documentType")
Toast.makeText(this, "Document successfully captured", Toast.LENGTH_SHORT).show()
}
data class DocumentImages(
var frontOriginalUri: Uri? = null,
var frontCropUri: Uri? = null,
var backOriginalUri: Uri? = null,
var backCropUri: Uri? = null,
var frontOriginalBase64: String? = null,
var frontCropBase64: String? = null,
var backOriginalBase64: String? = null,
var backCropBase64: String? = null
)
private fun processCompleteDocument(images: DocumentImages, type: String) {
// Create payload for backend
val payload = mapOf(
"documentType" to type,
"timestamp" to System.currentTimeMillis(),
"images" to mapOf(
"frontOriginal" to (images.frontOriginalBase64 ?: ""),
"frontCrop" to (images.frontCropBase64 ?: ""),
"backOriginal" to (images.backOriginalBase64 ?: ""),
"backCrop" to (images.backCropBase64 ?: "")
),
"metadata" to mapOf(
"platform" to "android",
"sdkVersion" to "1.0.0-beta"
)
)
// Send to backend or process locally
Log.d("DocumentPayload", "Retirement ready for processing: ${payload.keys}")
}
4.2 Errors Management
override fun onErrorStamps(text: String) {
Log.e("StampsError", "Reaping error: $text")
val errorMessage = when {
text.contains("permission", ignoreCase = true) -> "Required camera permits"
text.contains("camera", ignoreCase = true) -> "Camera Error - Check that it isn't in use"
text.contains("model", ignoreCase = true) -> "Error Loading A models - Reboot App"
text.contains("detection", ignoreCase = true) -> "Undetected document - Enhancement of light"
else -> "Error: $text"
}
Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show()
}
Step 5: Test document capture
Objective
Check that all functionalities of SDK They work properly.
checklist
| Element | State | Description |
|---|---|---|
| Camera permits | ☐ | App automatically requests and gets permission |
| Automatic detection | ☐ | AI detects document automatically |
| Imaging | ☐ | Original and face / back crop |
| Basal conversion64 | ☐ | All images are turned right |
| Errors management | ☐ | Mistakes are handled correctly |
Test process
Step A: Install and Configure
- Unit at Android Studio
- Install onto physical device (non-emulator)
- Check permits are requested automatically
Step B: Test Simple Capture
- Open Application and press "Scan Document"
- Position document within the framework
- To expect automatic detection (Green frame)
- Check automatic capture from the front
- Review login to confirm processing
Step C: Test Double face document
- Use INE or similar document with two faces
- Capture front automatically
- Following instructions to turn document
- Capture Reverse automatically
- Check 4 images at login
Problem solving
Common problems
| Problem | Resolution |
|---|---|
| "SDK Non-compatible " | Check minSdk 23 + and dependencies |
| "Camera doesn't start." | Check permissions and physical device |
| "Non-Loading Model" | Reboot app and check memory |
| "Capture doesn't work." | Using physical device, non-emulator |
Advanced Debug
private fun enableDebugMode() {
stampsSDK.setShowDebugMode(true)
stampsSDK.setShowConfigPanel(true)
Log.d("Debug", "= = = DIRECT INFORMATION = = =")
Log.d("Debug", "Modelo: ${Build.MODEL}")
Log.d("Debug", "SDK: ${Build.VERSION.SDK_INT}")
Log.d("Debug", "RAM disponible: ${getAvailableMemory()}")
}
private fun getAvailableMemory(): String {
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val memoryInfo = ActivityManager.MemoryInfo()
activityManager.getMemoryInfo(memoryInfo)
return "${memoryInfo.availMem / (1024 * 1024)} MB"
}
Full reference
Methods SDK
| Method | Description | Example |
|---|---|---|
| StampsSDK (context, list) | Building SDK | StampsSDK(this, this) |
| startStamps (model) | Start capture (Mode 1: AI) | stampsSDK.startStamps(1) |
| SetAlignmentTolerance (int) | Linkage tolerance (0-100) | stampsSDK.setAlignmentTolerance(15) |
| setMaskSize (int) | Detection area (50-100) | stampsSDK.setMaskSize(80) |
| SetCropMargin (int) | Filling mark (0-100) | stampsSDK.setCropMargin(100) |
| setCaptureDelay (int) | Delay before capture (ms) | stampsSDK.setCaptureDelay(1500) |
Available
| Configure | Description | When to use |
|---|---|---|
| SetAutoStampsClassification (true) | Automatic classification with A | Smart type detection |
| SetCameraFacing (BACK) | Camera selection | Rear camera for best quality |
| SetShowDebug | Real-time technologies | During development |
Response structure
| Image | Description | When it's generated |
|---|---|---|
| front | Full face image | Always |
| frontCropUri | Front cut by AI | When detecting document |
| backOriginalUri | Full back image | Double face only |
| backCropUri | Reverse cut by AI | When you get back |
Advanced Problem Resolution
Problem: "A model error"
// Check compatibility before using
private fun checkAICompatibility() {
val hasRequiredMemory = Runtime.getRuntime().maxMemory() > 512 * 1024 * 1024 // 512MB
val hasRequiredAPI = Build.VERSION.SDK_INT >= 23
if (!hasRequiredMemory || !hasRequiredAPI) {
showError("Uncompatible with AI models")
return
}
// Configure for low-resource devices
if (Runtime.getRuntime().maxMemory() < 1024 * 1024 * 1024) { // < 1GB
stampsSDK.setMaskSize(70) // Reduce processing area
stampsSDK.setCaptureDelay(2000) // More time to process
}
}
Problem: "Documents aren't detectable"
// Configure for better detection
private fun optimizeForDetection() {
stampsSDK.setAlignmentTolerance(20) // More tolerant
stampsSDK.setMaskSize(85) // bigger area
stampsSDK.setCaptureDelay(2000) // More time to stabilise
stampsSDK.setShowDebugMode(true) // See real-time metrics
}
Do you need some help?
Support information
- Problem description: What document are you trying to capture vs. what's going on?
- Android Studio Logs: Screenshots of errors in Lochcat
- Unit information: Model, Android version, RAM
- Free Configure: Unit versions
- Type of document: INE, passport, license, etc.
Support Contact
Email: support Always include: Android logs, built-up configuration, version SDK
You have successfully implemented JAAKStamps SDK Your app can now capture document automatically using artificial intelligence, generating optimization images for processes KYC.