Back to Blog
Developer Guide

Integrate the ChequeUI SDK in Your Mobile App

Step-by-step guide to integrate the ChequeUI SDK in your mobile app, covering iOS/Android setup, authentication, capture, extraction, and testing.

PublishedUpdated21 min readChequeUI Team

Step-by-Step: Integrating the ChequeUI SDK Into Your Mobile App

A comprehensive guide for iOS and Android developers implementing cheque capture and OCR capabilities


1. Introduction

The ChequeUI Mobile SDK empowers developers to integrate powerful cheque processing capabilities directly into their mobile applications. Whether you're building a banking app, a payment processing solution, or an accounting tool, the SDK provides everything you need to capture, validate, and extract data from cheques with enterprise-grade accuracy.

SDK Capabilities Overview

The ChequeUI SDK delivers a complete suite of cheque processing features:

  • Intelligent Capture: Auto-detection and capture of cheque boundaries with real-time edge detection
  • OCR Data Extraction: AI-powered recognition of all cheque fields including bank details, payee information, dates, and amounts
  • Amount Verification: Cross-validation between written and numeric amounts to detect discrepancies
  • Signature Detection: Computer vision-based signature presence verification
  • Batch Processing: Handle multiple cheques efficiently for bulk operations
  • Offline Support: Process cheques without network connectivity with automatic sync
  • Security-First Design: End-to-end encryption and secure token handling
  • Customizable UI: Theme the capture interface to match your brand identity

Why Choose ChequeUI SDK?

Unlike generic OCR solutions, ChequeUI is purpose-built for financial documents:

FeatureGeneric OCRChequeUI SDK
Field Accuracy70-80%95%+
Amount ValidationManualAutomatic
Edge DetectionBasicAdvanced with perspective correction
Bank RecognitionNoneBuilt-in database of 10,000+ banks
ComplianceSelf-managedSOC 2, PCI DSS ready
Integration Time2-4 weeks2-3 days

2. Prerequisites

Before integrating the ChequeUI SDK, ensure you have the following prerequisites in place.

System Requirements

iOS

  • Minimum Version: iOS 13.0+
  • Xcode: Version 14.0 or later
  • Swift: Version 5.5 or later
  • Device Capabilities: Camera with auto-focus, minimum 8MP recommended

Android

  • Minimum API Level: API 24 (Android 7.0)
  • Target API Level: API 34 (Android 14) recommended
  • Kotlin: Version 1.8.0 or later
  • Android Studio: Hedgehog (2023.1.1) or later
  • Device Capabilities: Camera2 API support, minimum 8MP recommended

API Credentials

To use the ChequeUI SDK, you need valid API credentials:

  1. Sign up at https://chequedb.com/developer
  2. Create a new project in the developer dashboard
  3. Generate API keys for your target platforms
  4. Configure allowed bundle IDs (iOS) and package names (Android)

Your credentials will include:

CHEQUEUI_API_KEY=your_api_key_here
CHEQUEUI_SECRET_KEY=your_secret_key_here
CHEQUEUI_ENVIRONMENT=sandbox  # or 'production'

Security Note: Never commit your SECRET_KEY to version control. Use environment variables or secure secret management.

Development Environment Setup

iOS Setup

  1. Ensure you have CocoaPods or Swift Package Manager installed
  2. Verify your provisioning profile includes camera permissions
  3. Add the following to your Info.plist:
<key>NSCameraUsageDescription</key>
<string>This app uses the camera to capture cheques for processing.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app accesses photos to upload cheque images.</string>

Android Setup

  1. Ensure your AndroidManifest.xml includes required permissions:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-feature android:name="android.hardware.camera" android:required="true" />
<uses-feature android:name="android.hardware.camera.autofocus" />
  1. For Android 6.0+, implement runtime permission requests for CAMERA permission.

3. Installation

iOS Installation

Using CocoaPods

Add the following to your Podfile:

platform :ios, '13.0'
use_frameworks!

target 'YourApp' do
  pod 'ChequeUI', '~> 2.5.0'
end

Then run:

pod install

Using Swift Package Manager

In Xcode, go to File → Add Package Dependencies and enter:

https://github.com/chequedb/chequeui-ios-sdk.git

Select version 2.5.0 or specify your version rule:

// Package.swift dependencies
dependencies: [
    .package(url: "https://github.com/chequedb/chequeui-ios-sdk.git", from: "2.5.0")
]

Android Installation

Using Gradle (build.gradle.kts)

Add the ChequeUI repository and dependency:

// settings.gradle.kts
pluginManagement {
    repositories {
        google()
        mavenCentral()
        maven { url = uri("https://chequedb.jfrog.io/artifactory/chequeui-gradle") }
    }
}

dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven { url = uri("https://chequedb.jfrog.io/artifactory/chequeui-gradle") }
    }
}
// app/build.gradle.kts
dependencies {
    implementation("com.chequedb:chequeui-sdk:2.5.0")
    implementation("com.chequedb:chequeui-camera:2.5.0")
    implementation("com.chequedb:chequeui-mlkit:2.5.0")
}

Using Maven

<repositories>
    <repository>
        <id>chequeui</id>
        <url>https://chequedb.jfrog.io/artifactory/chequeui-maven</url>
    </repository>
</repositories>

<dependencies>
    <dependency>
        <groupId>com.chequedb</groupId>
        <artifactId>chequeui-sdk</artifactId>
        <version>2.5.0</version>
    </dependency>
</dependencies>

Version Management

We recommend using a centralized version definition:

iOS (Podfile)

chequeui_version = '2.5.0'
pod 'ChequeUI', "~> #{chequeui_version}"

Android (gradle/libs.versions.toml)

[versions]
chequeui = "2.5.0"

[libraries]
chequeui-sdk = { module = "com.chequedb:chequeui-sdk", version.ref = "chequeui" }

4. Authentication Setup

API Key Configuration

iOS (Swift)

import ChequeUI

class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        
        let config = ChequeUIConfiguration(
            apiKey: "your_api_key_here",
            environment: .sandbox, // or .production
            region: .usEast
        )
        
        ChequeUI.initialize(with: config)
        return true
    }
}

Android (Kotlin)

import com.chequedb.chequeui.ChequeUI
import com.chequedb.chequeui.config.ChequeUIConfiguration

class MainApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        
        val config = ChequeUIConfiguration.Builder()
            .apiKey("your_api_key_here")
            .environment(Environment.SANDBOX) // or Environment.PRODUCTION
            .region(Region.US_EAST)
            .build()
        
        ChequeUI.initialize(this, config)
    }
}

Token Refresh

The SDK automatically handles token refresh. However, you can implement custom token handling for advanced use cases:

iOS

ChequeUI.shared.setTokenProvider { completion in
    // Your custom token fetching logic
    fetchFreshToken { token, error in
        completion(token, error)
    }
}

Android

ChequeUI.setTokenProvider(object : TokenProvider {
    override suspend fun getToken(): String {
        // Your custom token fetching logic
        return fetchFreshToken()
    }
    
    override suspend fun refreshToken(): String {
        return refreshAccessToken()
    }
})

Security Best Practices

  1. Environment Separation: Never use production credentials in development
  2. Key Storage: Use iOS Keychain or Android Keystore for sensitive values
  3. Certificate Pinning: Enable for production environments
  4. Obfuscation: Enable ProGuard/R8 on Android to protect SDK internals
// iOS - Enable certificate pinning
let config = ChequeUIConfiguration(
    apiKey: apiKey,
    environment: .production,
    certificatePinning: true,
    pinnedCertificates: ["sha256/..."]
)
// Android - Enable certificate pinning
val config = ChequeUIConfiguration.Builder()
    .apiKey(apiKey)
    .environment(Environment.PRODUCTION)
    .certificatePinning(true)
    .addPinnedCertificate("sha256/...")
    .build()

5. Basic Integration

SDK Initialization

Proper initialization ensures the SDK is ready before any cheque operations:

iOS

import ChequeUI

class ChequeService {
    static let shared = ChequeService()
    
    func initializeSDK() {
        guard ChequeUI.isInitialized else {
            print("SDK not initialized")
            return
        }
        
        // Configure logging
        ChequeUI.shared.logLevel = .debug
        
        // Set default options
        ChequeUI.shared.defaultCaptureOptions = CaptureOptions(
            autoCapture: true,
            guidanceMode: .enhanced,
            compressionQuality: 0.9
        )
    }
}

Android

import com.chequedb.chequeui.ChequeUI
import com.chequedb.chequeui.config.CaptureOptions

class ChequeService(private val context: Context) {
    
    fun initializeSDK() {
        if (!ChequeUI.isInitialized()) {
            throw IllegalStateException("SDK not initialized")
        }
        
        // Configure logging
        ChequeUI.setLogLevel(LogLevel.DEBUG)
        
        // Set default options
        val defaultOptions = CaptureOptions.Builder()
            .autoCapture(true)
            .guidanceMode(GuidanceMode.ENHANCED)
            .compressionQuality(0.9f)
            .build()
        
        ChequeUI.setDefaultCaptureOptions(defaultOptions)
    }
}

Configuration Options

OptionTypeDefaultDescription
autoCaptureBooleantrueAutomatically capture when edges are detected
guidanceModeEnum.standardVisual guidance overlay complexity
compressionQualityFloat0.9JPEG compression (0.0-1.0)
maxImageDimensionInt2048Max width/height in pixels
enableFlashBooleantrueAllow flash toggle in UI
captureSoundBooleantruePlay shutter sound on capture

Error Handling Setup

Configure global error handling to catch SDK-level issues:

iOS

ChequeUI.shared.errorHandler = { error in
    switch error {
    case .unauthorized:
        // Handle authentication failure
        self.refreshCredentials()
    case .networkError(let underlying):
        // Handle connectivity issues
        self.showOfflineMode()
    case .rateLimitExceeded:
        // Implement exponential backoff
        self.scheduleRetry()
    default:
        print("ChequeUI Error: \(error.localizedDescription)")
    }
}

Android

ChequeUI.setErrorHandler { error ->
    when (error) {
        is ChequeUIError.Unauthorized -> refreshCredentials()
        is ChequeUIError.NetworkError -> showOfflineMode()
        is ChequeUIError.RateLimitExceeded -> scheduleRetry()
        else -> Log.e("ChequeUI", "Error: ${error.message}")
    }
}

6. Cheque Capture

Camera Integration

The SDK provides a ready-to-use camera view controller/activity with advanced features.

iOS

import ChequeUI
import UIKit

class CaptureViewController: UIViewController {
    
    func presentChequeCapture() {
        let captureVC = ChequeCaptureViewController()
        captureVC.delegate = self
        captureVC.captureOptions = CaptureOptions(
            autoCapture: true,
            guidanceMode: .enhanced,
            showFlashToggle: true
        )
        
        present(captureVC, animated: true)
    }
}

extension CaptureViewController: ChequeCaptureDelegate {
    func chequeCapture(
        _ controller: ChequeCaptureViewController,
        didCapture image: UIImage,
        withMetadata metadata: CaptureMetadata
    ) {
        // Handle captured image
        print("Capture quality score: \(metadata.qualityScore)")
        print("Edge detection confidence: \(metadata.edgeConfidence)")
        
        // Dismiss and process
        controller.dismiss(animated: true) {
            self.processCapturedCheque(image)
        }
    }
    
    func chequeCapture(
        _ controller: ChequeCaptureViewController,
        didFailWithError error: ChequeUIError
    ) {
        controller.dismiss(animated: true)
        showError(error)
    }
}

Android

import com.chequedb.chequeui.capture.ChequeCaptureActivity
import com.chequedb.chequeui.capture.CaptureContract

class MainActivity : AppCompatActivity() {
    
    private val captureLauncher = registerForActivityResult(
        CaptureContract()
    ) { result ->
        when (result) {
            is CaptureResult.Success -> {
                val image = result.image
                val metadata = result.metadata
                
                Log.d("ChequeUI", "Quality score: ${metadata.qualityScore}")
                Log.d("ChequeUI", "Edge confidence: ${metadata.edgeConfidence}")
                
                processCapturedCheque(image)
            }
            is CaptureResult.Error -> {
                showError(result.error)
            }
            is CaptureResult.Cancelled -> {
                Log.d("ChequeUI", "User cancelled capture")
            }
        }
    }
    
    fun launchCapture() {
        val options = CaptureOptions.Builder()
            .autoCapture(true)
            .guidanceMode(GuidanceMode.ENHANCED)
            .showFlashToggle(true)
            .build()
        
        captureLauncher.launch(options)
    }
}

Auto-Capture vs. Manual

The SDK supports both automatic and manual capture modes:

// Auto-capture: Triggered when edges are stable and quality is good
let autoOptions = CaptureOptions(autoCapture: true, stabilityThreshold: 0.95)

// Manual capture: User taps shutter button
let manualOptions = CaptureOptions(autoCapture: false)

Auto-capture requirements:

  • Edge detection confidence ≥ 85%
  • Image stability for 1.5 seconds
  • Minimum brightness threshold met
  • No motion blur detected

Image Quality Validation

The SDK performs real-time quality checks:

// Quality thresholds
let options = CaptureOptions(
    minQualityScore: 0.75,        // Overall image quality
    minEdgeConfidence: 0.80,      // Edge detection confidence
    minBrightness: 0.3,           // Relative brightness (0-1)
    maxBlurScore: 0.4,            // Blur detection (lower is sharper)
    requireAllCorners: true       // All four corners must be visible
)

Edge Detection

Advanced edge detection with perspective correction:

// Configure edge detection sensitivity
val edgeOptions = EdgeDetectionOptions.Builder()
    .sensitivity(Sensitivity.HIGH)
    .perspectiveCorrection(true)
    .cornerRefinement(true)
    .build()

val captureOptions = CaptureOptions.Builder()
    .edgeDetection(edgeOptions)
    .build()

7. Data Extraction

Synchronous vs. Async Processing

Choose the processing mode based on your use case:

Synchronous (Real-time)

// iOS - Synchronous extraction
do {
    let result = try await ChequeUI.shared.extract(
        from: image,
        options: ExtractionOptions(syncMode: true)
    )
    updateUI(with: result)
} catch {
    handleExtractionError(error)
}
// Android - Synchronous extraction
lifecycleScope.launch {
    try {
        val result = ChequeUI.extract(
            image = image,
            options = ExtractionOptions(syncMode = true)
        )
        updateUI(result)
    } catch (e: ExtractionException) {
        handleExtractionError(e)
    }
}

Asynchronous (Batch/Background)

// Submit for background processing
let jobId = try await ChequeUI.shared.submitExtraction(
    image: image,
    webhookUrl: "https://your-api.com/webhooks/cheque"
)

// Check status later
let status = try await ChequeUI.shared.checkExtractionStatus(jobId: jobId)

Field Mapping

Map extracted fields to your data models:

struct ChequeData: Codable {
    let bankName: String
    let branch: String
    let date: Date
    let payee: String
    let amountInWords: String
    let amountInNumerals: Decimal
    let accountNumber: String?
    let chequeNumber: String?
}

// Field mapping configuration
let mapping = FieldMapping()
    .map(.bankName, to: \.bankName)
    .map(.branch, to: \.branch)
    .map(.date, to: \.date, formatter: dateFormatter)
    .map(.amountInWords, to: \.amountInWords)
    .map(.amountInNumerals, to: \.amountInNumerals)

let result = try await ChequeUI.shared.extract(
    from: image,
    mapping: mapping
)

Confidence Thresholds

Set minimum confidence levels for field extraction:

let extractionOptions = ExtractionOptions(
    confidenceThresholds: [
        .bankName: 0.85,
        .amountInNumerals: 0.95,
        .amountInWords: 0.90,
        .date: 0.80,
        .payee: 0.75
    ],
    rejectOnLowConfidence: true
)

8. Error Handling

Network Errors

extension ChequeViewController {
    func handleNetworkError(_ error: ChequeUIError) {
        switch error {
        case .networkError(let urlError):
            switch urlError.code {
            case .notConnectedToInternet:
                enableOfflineMode()
            case .timedOut:
                showRetryDialog(message: "Connection timed out")
            default:
                showError("Network error: \(urlError.localizedDescription)")
            }
        default:
            break
        }
    }
}

Poor Image Quality

func handleQualityError(_ error: ChequeUIError) {
    switch error {
    case .poorImageQuality(let issues):
        var messages: [String] = []
        
        if issues.contains(.blurry) {
            messages.append("Image is blurry. Hold steady and retake.")
        }
        if issues.contains(.poorLighting) {
            messages.append("Insufficient lighting. Try using flash.")
        }
        if issues.contains(.edgesNotDetected) {
            messages.append("Cannot detect cheque edges. Place on dark background.")
        }
        if issues.contains(.glareDetected) {
            messages.append("Glare detected. Adjust angle to avoid reflections.")
        }
        
        showGuidance(messages: messages)
        
    default:
        break
    }
}

Invalid Cheques

func validateCheque(_ result: ExtractionResult) -> ValidationStatus {
    // Check amount matching
    guard result.amountsMatch else {
        return .invalid(reason: "Written and numeric amounts do not match")
    }
    
    // Check date validity
    guard result.date <= Date() else {
        return .invalid(reason: "Cheque date is in the future")
    }
    
    // Check expiry (typically 6 months)
    let sixMonthsAgo = Calendar.current.date(byAdding: .month, value: -6, to: Date())!
    guard result.date >= sixMonthsAgo else {
        return .invalid(reason: "Cheque may be stale-dated")
    }
    
    return .valid
}

Retry Logic

Implement exponential backoff for transient failures:

class ChequeProcessor {
    private val maxRetries = 3
    private val baseDelay = 1000L // 1 second
    
    suspend fun processWithRetry(image: Bitmap): ExtractionResult {
        var lastError: Exception? = null
        
        repeat(maxRetries) { attempt ->
            try {
                return ChequeUI.extract(image)
            } catch (e: NetworkException) {
                lastError = e
                if (attempt < maxRetries - 1) {
                    val delay = baseDelay * (2.0.pow(attempt)).toLong()
                    delay(delay)
                }
            }
        }
        
        throw lastError ?: Exception("Extraction failed")
    }
}

9. Advanced Features

Batch Processing

Process multiple cheques efficiently:

// iOS Batch Processing
let batchRequest = BatchExtractionRequest(
    images: chequeImages,
    options: BatchOptions(
        parallelProcessing: true,
        maxConcurrent: 3,
        continueOnError: true
    )
)

let batchResult = try await ChequeUI.shared.extractBatch(request: batchRequest)

for (index, result) in batchResult.results.enumerated() {
    switch result {
    case .success(let extraction):
        print("Cheque \(index): Extracted \(extraction.payee)")
    case .failure(let error):
        print("Cheque \(index): Failed - \(error)")
    }
}
// Android Batch Processing
val batchRequest = BatchExtractionRequest(
    images = chequeImages,
    options = BatchOptions(
        parallelProcessing = true,
        maxConcurrent = 3,
        continueOnError = true
    )
)

ChequeUI.extractBatch(batchRequest)
    .onEach { progress ->
        updateProgress(progress.completed, progress.total)
    }
    .collect { result ->
        when (result) {
            is BatchResult.Success -> processExtraction(result.extraction)
            is BatchResult.Failure -> logFailedCheque(result.error)
        }
    }

Offline Mode

Enable offline processing with automatic sync:

// Configure offline support
let config = ChequeUIConfiguration(
    apiKey: apiKey,
    environment: .production,
    offlineMode: .enabled(
        maxStorageMB: 100,
        autoSync: true,
        syncOnWifiOnly: false
    )
)

// Queue for offline processing
ChequeUI.shared.queueForOffline(image: image, metadata: metadata)

// Listen for sync events
ChequeUI.shared.offlineSyncDelegate = self

extension MyClass: OfflineSyncDelegate {
    func offlineSync(didCompleteSync results: [OfflineResult]) {
        for result in results {
            print("Synced cheque: \(result.chequeId)")
        }
    }
}

Custom UI Theming

Customize the capture interface to match your brand:

// iOS Theming
var theme = ChequeUITheme()
theme.primaryColor = UIColor.systemIndigo
theme.accentColor = UIColor.systemOrange
theme.backgroundColor = UIColor.systemBackground
theme.textColor = UIColor.label
theme.cornerRadius = 12.0
theme.font = UIFont.systemFont(ofSize: 16, weight: .medium)

// Overlay customization
theme.overlayStyle = .roundedRectangle(cornerRadius: 20)
theme.guideColor = UIColor.green.withAlphaComponent(0.7)
theme.guideWidth = 3.0

ChequeUI.shared.applyTheme(theme)
// Android Theming
val theme = ChequeUITheme.Builder()
    .primaryColor(ContextCompat.getColor(context, R.color.brand_primary))
    .accentColor(ContextCompat.getColor(context, R.color.brand_accent))
    .backgroundColor(ContextCompat.getColor(context, R.color.background))
    .textColor(ContextCompat.getColor(context, R.color.text_primary))
    .cornerRadius(12f)
    .fontFamily(R.font.brand_font)
    .overlayStyle(OverlayStyle.ROUNDED_RECTANGLE)
    .guideColor(ContextCompat.getColor(context, R.color.guide_green))
    .build()

ChequeUI.applyTheme(theme)

10. Testing

Unit Tests

Test your integration logic:

// iOS Unit Tests
import XCTest
@testable import ChequeUI

class ChequeServiceTests: XCTestCase {
    
    var chequeService: ChequeService!
    var mockChequeUI: MockChequeUI!
    
    override func setUp() {
        super.setUp()
        mockChequeUI = MockChequeUI()
        chequeService = ChequeService(chequeUI: mockChequeUI)
    }
    
    func testAmountValidation() async {
        // Given
        let extraction = ExtractionResult(
            amountInWords: "One Thousand Dollars",
            amountInNumerals: 1000.00,
            amountsMatch: true
        )
        mockChequeUI.mockResult = extraction
        
        // When
        let result = try? await chequeService.processCheque(image: testImage)
        
        // Then
        XCTAssertNotNil(result)
        XCTAssertEqual(result?.amount, 1000.00)
        XCTAssertTrue(result?.isValid ?? false)
    }
    
    func testAmountMismatchDetection() async {
        // Given
        let extraction = ExtractionResult(
            amountInWords: "One Thousand Dollars",
            amountInNumerals: 100.00,
            amountsMatch: false
        )
        mockChequeUI.mockResult = extraction
        
        // When
        let result = try? await chequeService.processCheque(image: testImage)
        
        // Then
        XCTAssertNotNil(result)
        XCTAssertFalse(result?.isValid ?? true)
    }
}
// Android Unit Tests
@RunWith(MockitoJUnitRunner::class)
class ChequeServiceTest {
    
    @Mock
    lateinit var mockChequeUI: ChequeUI
    
    lateinit var chequeService: ChequeService
    
    @Before
    fun setup() {
        chequeService = ChequeService(mockChequeUI)
    }
    
    @Test
    fun `amount validation succeeds when amounts match`() = runTest {
        // Given
        val extraction = ExtractionResult(
            amountInWords = "One Thousand Dollars",
            amountInNumerals = BigDecimal("1000.00"),
            amountsMatch = true
        )
        whenever(mockChequeUI.extract(any(), any())).thenReturn(extraction)
        
        // When
        val result = chequeService.processCheque(testImage)
        
        // Then
        assertTrue(result.isValid)
        assertEquals(BigDecimal("1000.00"), result.amount)
    }
}

Integration Tests

Test end-to-end flows:

// iOS UI Tests
class ChequeCaptureUITests: XCTestCase {
    
    var app: XCUIApplication!
    
    override func setUp() {
        super.setUp()
        app = XCUIApplication()
        app.launchArguments = ["--uitesting", "--mock-camera"]
        app.launch()
    }
    
    func testCompleteCaptureFlow() {
        // Tap capture button
        app.buttons["Capture Cheque"].tap()
        
        // Wait for camera
        XCTAssertTrue(app.otherElements["ChequeCameraView"].waitForExistence(timeout: 5))
        
        // Trigger mock capture
        app.buttons["MockCapture"].tap()
        
        // Verify results screen
        XCTAssertTrue(app.staticTexts["Extraction Results"].waitForExistence(timeout: 10))
        XCTAssertTrue(app.staticTexts["Bank Name"].exists)
    }
}

Mock Responses

// Mock ChequeUI for testing
class MockChequeUI: ChequeUIProtocol {
    var mockResult: ExtractionResult?
    var mockError: Error?
    
    func extract(from image: UIImage, options: ExtractionOptions?) async throws -> ExtractionResult {
        if let error = mockError {
            throw error
        }
        return mockResult ?? ExtractionResult()
    }
}

11. Production Deployment

Performance Tuning

Optimize for production workloads:

// iOS Performance Configuration
let perfConfig = PerformanceConfig(
    imageCompression: 0.85,      // Balance quality vs size
    maxImageDimension: 1920,     // Reduce processing time
    cacheSizeMB: 50,             // Enable response caching
    enableImagePreprocessing: true // Auto-enhance before OCR
)

ChequeUI.shared.configurePerformance(perfConfig)
// Android Performance Configuration
val perfConfig = PerformanceConfig.Builder()
    .imageCompression(0.85f)
    .maxImageDimension(1920)
    .cacheSizeMB(50)
    .enableImagePreprocessing(true)
    .bitmapPoolEnabled(true)     // Reuse bitmap allocations
    .build()

ChequeUI.configurePerformance(perfConfig)

Monitoring

Integrate with your analytics platform:

// iOS Analytics Integration
ChequeUI.shared.analyticsDelegate = self

extension MyApp: ChequeUIAnalyticsDelegate {
    func chequeUI(didCaptureCheque event: CaptureEvent) {
        Analytics.track("cheque_captured", properties: [
            "quality_score": event.qualityScore,
            "processing_time_ms": event.processingTime,
            "auto_capture": event.usedAutoCapture
        ])
    }
    
    func chequeUI(didExtractData event: ExtractionEvent) {
        Analytics.track("cheque_extracted", properties: [
            "confidence_avg": event.averageConfidence,
            "fields_extracted": event.fieldCount,
            "amounts_matched": event.amountsMatch
        ])
    }
}

Analytics Dashboard Metrics

Track these key metrics in production:

MetricTargetDescription
Capture Success Rate>95%Percentage of successful captures
Extraction Accuracy>92%Field-level OCR accuracy
End-to-End Latency<3sCapture to extraction complete
Amount Match Rate>98%Written vs numeric agreement
SDK Crash Rate<0.1%Stability metric

12. Complete Code Examples

iOS (Swift) - Complete Integration

import UIKit
import ChequeUI

class ChequeScannerViewController: UIViewController {
    
    // MARK: - Properties
    private let chequeService = ChequeService()
    private var currentImage: UIImage?
    
    // MARK: - UI Components
    private lazy var captureButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Capture Cheque", for: .normal)
        button.backgroundColor = .systemIndigo
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 12
        button.addTarget(self, action: #selector(captureTapped), for: .touchUpInside)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    private lazy var resultView: ChequeResultView = {
        let view = ChequeResultView()
        view.isHidden = true
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    // MARK: - Lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        initializeSDK()
    }
    
    // MARK: - Setup
    private func setupUI() {
        view.backgroundColor = .systemBackground
        title = "Cheque Scanner"
        
        view.addSubview(captureButton)
        view.addSubview(resultView)
        
        NSLayoutConstraint.activate([
            captureButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            captureButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            captureButton.widthAnchor.constraint(equalToConstant: 200),
            captureButton.heightAnchor.constraint(equalToConstant: 50),
            
            resultView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
            resultView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            resultView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            resultView.bottomAnchor.constraint(equalTo: captureButton.topAnchor, constant: -20)
        ])
    }
    
    private func initializeSDK() {
        guard ChequeUI.isInitialized else {
            showAlert(message: "SDK not initialized")
            return
        }
        
        ChequeUI.shared.logLevel = .info
        ChequeUI.shared.errorHandler = { [weak self] error in
            self?.handleSDKError(error)
        }
    }
    
    // MARK: - Actions
    @objc private func captureTapped() {
        presentChequeCapture()
    }
    
    private func presentChequeCapture() {
        let captureVC = ChequeCaptureViewController()
        captureVC.delegate = self
        captureVC.captureOptions = CaptureOptions(
            autoCapture: true,
            guidanceMode: .enhanced,
            minQualityScore: 0.75,
            minEdgeConfidence: 0.80
        )
        present(captureVC, animated: true)
    }
    
    // MARK: - Processing
    private func processCheque(_ image: UIImage) {
        showLoading(true)
        
        Task {
            do {
                let extraction = try await ChequeUI.shared.extract(
                    from: image,
                    options: ExtractionOptions(
                        confidenceThresholds: [
                            .amountInNumerals: 0.95,
                            .amountInWords: 0.90
                        ]
                    )
                )
                
                await MainActor.run {
                    self.showLoading(false)
                    self.displayResults(extraction)
                }
                
            } catch {
                await MainActor.run {
                    self.showLoading(false)
                    self.handleExtractionError(error)
                }
            }
        }
    }
    
    private func displayResults(_ result: ExtractionResult) {
        resultView.configure(with: result)
        resultView.isHidden = false
        
        // Log analytics
        Analytics.track("cheque_processed", properties: [
            "bank": result.bankName,
            "amount": result.amountInNumerals,
            "confidence": result.averageConfidence
        ])
    }
    
    // MARK: - Error Handling
    private func handleSDKError(_ error: ChequeUIError) {
        switch error {
        case .unauthorized:
            showAlert(message: "Session expired. Please log in again.")
        case .networkError:
            showAlert(message: "Network error. Please check your connection.")
        case .rateLimitExceeded:
            showAlert(message: "Too many requests. Please try again later.")
        default:
            showAlert(message: error.localizedDescription)
        }
    }
    
    private func handleExtractionError(_ error: Error) {
        if let chequeError = error as? ChequeUIError {
            switch chequeError {
            case .poorImageQuality(let issues):
                let message = issues.map { $0.description }.joined(separator: "\n")
                showAlert(message: "Image quality issues:\n\(message)")
            default:
                showAlert(message: chequeError.localizedDescription)
            }
        } else {
            showAlert(message: "An unexpected error occurred")
        }
    }
    
    private func showLoading(_ loading: Bool) {
        captureButton.isEnabled = !loading
        captureButton.setTitle(loading ? "Processing..." : "Capture Cheque", for: .normal)
    }
    
    private func showAlert(message: String) {
        let alert = UIAlertController(title: "Notice", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
}

// MARK: - ChequeCaptureDelegate
extension ChequeScannerViewController: ChequeCaptureDelegate {
    func chequeCapture(
        _ controller: ChequeCaptureViewController,
        didCapture image: UIImage,
        withMetadata metadata: CaptureMetadata
    ) {
        controller.dismiss(animated: true) { [weak self] in
            self?.currentImage = image
            self?.processCheque(image)
        }
    }
    
    func chequeCapture(
        _ controller: ChequeCaptureViewController,
        didFailWithError error: ChequeUIError
    ) {
        controller.dismiss(animated: true) { [weak self] in
            self?.handleSDKError(error)
        }
    }
}

Android (Kotlin) - Complete Integration

package com.example.chequeapp

import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.chequedb.chequeui.ChequeUI
import com.chequedb.chequeui.capture.CaptureContract
import com.chequedb.chequeui.capture.CaptureOptions
import com.chequedb.chequeui.capture.CaptureResult
import com.chequedb.chequeui.config.ChequeUIConfiguration
import com.chequedb.chequeui.config.Environment
import com.chequedb.chequeui.config.ExtractionOptions
import com.chequedb.chequeui.config.GuidanceMode
import com.chequedb.chequeui.error.ChequeUIError
import com.chequedb.chequeui.model.ExtractionResult
import com.example.chequeapp.databinding.ActivityChequeScannerBinding
import kotlinx.coroutines.launch

class ChequeScannerActivity : AppCompatActivity() {

    private lateinit var binding: ActivityChequeScannerBinding
    
    private val captureLauncher = registerForActivityResult(CaptureContract()) { result ->
        when (result) {
            is CaptureResult.Success -> {
                processCheque(result.image)
            }
            is CaptureResult.Error -> {
                handleCaptureError(result.error)
            }
            is CaptureResult.Cancelled -> {
                Toast.makeText(this, "Capture cancelled", Toast.LENGTH_SHORT).show()
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityChequeScannerBinding.inflate(layoutInflater)
        setContentView(binding.root)
        
        initializeSDK()
        setupUI()
    }

    private fun initializeSDK() {
        if (!ChequeUI.isInitialized()) {
            val config = ChequeUIConfiguration.Builder()
                .apiKey(BuildConfig.CHEQUEUI_API_KEY)
                .environment(if (BuildConfig.DEBUG) Environment.SANDBOX else Environment.PRODUCTION)
                .build()
            ChequeUI.initialize(applicationContext, config)
        }
        
        ChequeUI.setLogLevel(LogLevel.INFO)
        ChequeUI.setErrorHandler { error ->
            handleSDKError(error)
        }
    }

    private fun setupUI() {
        binding.captureButton.setOnClickListener {
            launchCapture()
        }
        
        binding.retryButton.setOnClickListener {
            launchCapture()
        }
        
        binding.resultView.isVisible = false
    }

    private fun launchCapture() {
        val options = CaptureOptions.Builder()
            .autoCapture(true)
            .guidanceMode(GuidanceMode.ENHANCED)
            .minQualityScore(0.75f)
            .minEdgeConfidence(0.80f)
            .showFlashToggle(true)
            .build()
        
        captureLauncher.launch(options)
    }

    private fun processCheque(image: Bitmap) {
        showLoading(true)
        
        lifecycleScope.launch {
            try {
                val extraction = ChequeUI.extract(
                    image = image,
                    options = ExtractionOptions(
                        confidenceThresholds = mapOf(
                            ExtractionField.AMOUNT_IN_NUMERALS to 0.95f,
                            ExtractionField.AMOUNT_IN_WORDS to 0.90f,
                            ExtractionField.BANK_NAME to 0.85f
                        )
                    )
                )
                
                showLoading(false)
                displayResults(extraction)
                
            } catch (e: Exception) {
                showLoading(false)
                handleExtractionError(e)
            }
        }
    }

    private fun displayResults(result: ExtractionResult) {
        binding.resultView.apply {
            isVisible = true
            setBankName(result.bankName)
            setBranch(result.branch)
            setPayee(result.payee)
            setAmount("${result.amountInWords} / $${result.amountInNumerals}")
            setDate(result.date)
            setConfidence(result.averageConfidence)
            setAmountsMatch(result.amountsMatch)
        }
        
        // Log analytics
        Analytics.logEvent("cheque_processed") {
            param("bank", result.bankName)
            param("amount", result.amountInNumerals.toDouble())
            param("confidence", result.averageConfidence.toDouble())
        }
    }

    private fun handleSDKError(error: ChequeUIError) {
        val message = when (error) {
            is ChequeUIError.Unauthorized -> "Session expired. Please log in again."
            is ChequeUIError.NetworkError -> "Network error. Please check your connection."
            is ChequeUIError.RateLimitExceeded -> "Too many requests. Please try again later."
            else -> error.message ?: "An error occurred"
        }
        
        runOnUiThread {
            Toast.makeText(this, message, Toast.LENGTH_LONG).show()
        }
    }

    private fun handleCaptureError(error: ChequeUIError) {
        when (error) {
            is ChequeUIError.CameraPermissionDenied -> {
                Toast.makeText(this, "Camera permission required", Toast.LENGTH_LONG).show()
            }
            else -> handleSDKError(error)
        }
    }

    private fun handleExtractionError(error: Exception) {
        val message = when (error) {
            is ChequeUIError.PoorImageQuality -> {
                val issues = error.issues.joinToString("\n") { "• ${it.description}" }
                "Image quality issues:\n$issues"
            }
            is ChequeUIError.LowConfidence -> {
                "Could not read cheque clearly. Please retake."
            }
            else -> "Failed to process cheque. Please try again."
        }
        
        Toast.makeText(this, message, Toast.LENGTH_LONG).show()
        binding.errorView.isVisible = true
    }

    private fun showLoading(loading: Boolean) {
        binding.captureButton.isEnabled = !loading
        binding.progressBar.visibility = if (loading) View.VISIBLE else View.GONE
        binding.captureButton.text = if (loading) "Processing..." else "Capture Cheque"
    }
}

13. Troubleshooting Guide

Common Issues and Solutions

IssuePossible CauseSolution
SDK Initialization FailedInvalid API keyVerify credentials in developer dashboard
Camera Not OpeningPermission deniedCheck Info.plist / AndroidManifest.xml
Poor OCR AccuracyImage quality issuesUse auto-capture and quality validation
Slow ProcessingLarge image sizeReduce maxImageDimension in config
Token ExpirationClock skewSync device time with NTP
Build ErrorsVersion conflictsUpdate to latest SDK version

Debugging Tips

  1. Enable Verbose Logging:

    ChequeUI.shared.logLevel = .verbose
    
  2. Check SDK Version:

    print("ChequeUI SDK Version: \(ChequeUI.version)")
    
  3. Validate Configuration:

    let validation = ChequeUI.shared.validateConfiguration()
    print(validation.diagnostics)
    

Support Resources


14. Conclusion

The ChequeUI Mobile SDK provides a comprehensive, production-ready solution for integrating cheque processing capabilities into your iOS and Android applications. With its intelligent capture features, high-accuracy OCR, and extensive customization options, you can deliver a seamless cheque processing experience to your users.

Key Takeaways

  1. Quick Integration: Get started in hours, not weeks, with our intuitive SDK
  2. Production-Ready: Built-in error handling, offline support, and security features
  3. High Accuracy: Purpose-built ML models optimized for financial documents
  4. Flexible Architecture: Support for both synchronous and asynchronous workflows
  5. Comprehensive Support: Extensive documentation, examples, and dedicated support

Next Steps

  1. Sign up for a developer account at chequedb.com/developer
  2. Download the sample apps from our GitHub repositories
  3. Experiment with the sandbox environment
  4. Integrate the SDK into your application
  5. Deploy to production with confidence

Additional Resources


Happy coding! If you have any questions or feedback, we'd love to hear from you at developers@chequedb.com

Turn This Into A Production Workflow

Explore implementation pages used by banks and businesses for cheque capture, MICR extraction, and end-to-end automation.

Share this article

Help others discover this content

Ready to Modernize Your Cheque Processing?

Discover how Chequedb can help you automate cheque processing, prevent fraud, and ensure compliance.