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:
| Feature | Generic OCR | ChequeUI SDK |
|---|---|---|
| Field Accuracy | 70-80% | 95%+ |
| Amount Validation | Manual | Automatic |
| Edge Detection | Basic | Advanced with perspective correction |
| Bank Recognition | None | Built-in database of 10,000+ banks |
| Compliance | Self-managed | SOC 2, PCI DSS ready |
| Integration Time | 2-4 weeks | 2-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:
- Sign up at https://chequedb.com/developer
- Create a new project in the developer dashboard
- Generate API keys for your target platforms
- 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_KEYto version control. Use environment variables or secure secret management.
Development Environment Setup
iOS Setup
- Ensure you have CocoaPods or Swift Package Manager installed
- Verify your provisioning profile includes camera permissions
- 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
- Ensure your
AndroidManifest.xmlincludes 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" />
- For Android 6.0+, implement runtime permission requests for
CAMERApermission.
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
- Environment Separation: Never use production credentials in development
- Key Storage: Use iOS Keychain or Android Keystore for sensitive values
- Certificate Pinning: Enable for production environments
- 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
| Option | Type | Default | Description |
|---|---|---|---|
autoCapture | Boolean | true | Automatically capture when edges are detected |
guidanceMode | Enum | .standard | Visual guidance overlay complexity |
compressionQuality | Float | 0.9 | JPEG compression (0.0-1.0) |
maxImageDimension | Int | 2048 | Max width/height in pixels |
enableFlash | Boolean | true | Allow flash toggle in UI |
captureSound | Boolean | true | Play 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:
| Metric | Target | Description |
|---|---|---|
| Capture Success Rate | >95% | Percentage of successful captures |
| Extraction Accuracy | >92% | Field-level OCR accuracy |
| End-to-End Latency | <3s | Capture 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
| Issue | Possible Cause | Solution |
|---|---|---|
| SDK Initialization Failed | Invalid API key | Verify credentials in developer dashboard |
| Camera Not Opening | Permission denied | Check Info.plist / AndroidManifest.xml |
| Poor OCR Accuracy | Image quality issues | Use auto-capture and quality validation |
| Slow Processing | Large image size | Reduce maxImageDimension in config |
| Token Expiration | Clock skew | Sync device time with NTP |
| Build Errors | Version conflicts | Update to latest SDK version |
Debugging Tips
-
Enable Verbose Logging:
ChequeUI.shared.logLevel = .verbose -
Check SDK Version:
print("ChequeUI SDK Version: \(ChequeUI.version)") -
Validate Configuration:
let validation = ChequeUI.shared.validateConfiguration() print(validation.diagnostics)
Support Resources
- Documentation: https://docs.chequedb.com/sdk
- Status Page: https://status.chequedb.com
- Support Email: sdk-support@chequedb.com
- GitHub Issues: github.com/chequedb/chequeui-sdk
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
- Quick Integration: Get started in hours, not weeks, with our intuitive SDK
- Production-Ready: Built-in error handling, offline support, and security features
- High Accuracy: Purpose-built ML models optimized for financial documents
- Flexible Architecture: Support for both synchronous and asynchronous workflows
- Comprehensive Support: Extensive documentation, examples, and dedicated support
Next Steps
- Sign up for a developer account at chequedb.com/developer
- Download the sample apps from our GitHub repositories
- Experiment with the sandbox environment
- Integrate the SDK into your application
- 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