1. Kotlin
  2. Model Management API

Kotlin

Model Management API

The Model Management API provides fine-grained control over OCR model lifecycle, allowing you to download, load, unload, and manage AI models independently from the scanning process.

Overview

Key Features

  • Separate Download & Load: Download models during onboarding, load them when needed
  • Pre-download for Offline: Enable offline-first user experiences
  • Query Models: Check downloaded/loaded models with detailed metadata
  • Update Management: Check for and download model updates
  • Memory Management: Explicit load/unload control for optimal memory usage
  • Dynamic OCR: Switch between different models at runtime

Benefits

  • Better User Experience: Pre-download models during onboarding instead of first use
  • Offline Support: Download models when connected, use them offline
  • Memory Efficiency: Load only needed models, unload when done
  • Update Control: Check and apply model updates programmatically

Quick Start

Initialize once in your MainActivity:

        class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // Initialize VisionSDK first
        VisionSDK.configure(this, "YOUR_API_KEY", Environment.PRODUCTION)
        
        // Initialize ModelManager
        ModelManager.initialize(this) {
            maxConcurrentDownloads(2)
            enableLogging(true)
            lifecycleListener(object : ModelLifecycleListener {
                override fun onDownloadCompleted(module: OCRModule) {
                    Log.d("ModelManager", "Downloaded: $module")
                }
                override fun onModelLoaded(module: OCRModule) {
                    Log.d("ModelManager", "Loaded: $module")
                }
            })
        }
    }
}

      

Then use anywhere in your app:

        val modelManager = ModelManager.getInstance()

lifecycleScope.launch {
    modelManager.downloadModel(
        module = OCRModule.ShippingLabel(ModelSize.Micro),
        apiKey = "YOUR_API_KEY",
        progressListener = { progress ->
            Log.d("Download", "Progress: ${(progress.progress * 100).toInt()}%")
        }
    )
}

      

Core Concepts

OCR Modules

Available OCR modules with their supported sizes:

Module Sizes Description
ShippingLabel Nano, Micro, Large Extract shipping label information
BillOfLading Large Extract bill of lading data
ItemLabel Large Extract item label information
DocumentClassification Micro, Large Classify document types

Example:

        // Create different module instances
val slNano = OCRModule.ShippingLabel(ModelSize.Nano)
val slMicro = OCRModule.ShippingLabel(ModelSize.Micro)
val slLarge = OCRModule.ShippingLabel(ModelSize.Large)

val bol = OCRModule.BillOfLading(ModelSize.Large)
val il = OCRModule.ItemLabel(ModelSize.Large)
val dc = OCRModule.DocumentClassification(ModelSize.Micro)

      

Initialization

Initialize ModelManager

Call once during app startup:

        ModelManager.initialize(applicationContext) {
    maxConcurrentDownloads(2)          // Max parallel downloads
    enableLogging(BuildConfig.DEBUG)    // Enable debug logs
    lifecycleListener(myListener)       // Optional lifecycle callbacks
}

      

Check Initialization

        if (ModelManager.isInitialized()) {
    val manager = ModelManager.getInstance()
} else {
    ModelManager.initialize(context)
}

      

Downloading Models

Basic Download

        lifecycleScope.launch {
    try {
        modelManager.downloadModel(
            module = OCRModule.ShippingLabel(ModelSize.Micro),
            apiKey = "pk_live_xxxxx"
        )
        Log.d("Download", "Complete!")
    } catch (e: ModelException) {
        Log.e("Download", "Failed: ${e.message}")
    }
}

      

Download with Progress

        lifecycleScope.launch {
    modelManager.downloadModel(
        module = OCRModule.ShippingLabel(ModelSize.Micro),
        apiKey = apiKey,
        progressListener = { progress ->
            runOnUiThread {
                progressBar.progress = (progress.progress * 100).toInt()
                statusText.text = "${(progress.progress * 100).toInt()}%"
            }
        }
    )
}

      

Download Multiple Models

        lifecycleScope.launch {
    val modules = listOf(
        OCRModule.ShippingLabel(ModelSize.Micro),
        OCRModule.DocumentClassification(ModelSize.Micro)
    )
    
    modules.forEachIndexed { index, module ->
        try {
            statusText.text = "Downloading ${index + 1}/${modules.size}"
            
            modelManager.downloadModel(
                module = module,
                apiKey = apiKey,
                progressListener = { progress ->
                    progressBar.progress = (progress.progress * 100).toInt()
                }
            )
        } catch (e: ModelException) {
            Log.e("Download", "Failed to download $module: ${e.message}")
            return@launch
        }
    }
    
    Toast.makeText(context, "All models downloaded!", Toast.LENGTH_SHORT).show()
}

      

Cancel Download

        val cancelled = modelManager.cancelDownload(
    OCRModule.ShippingLabel(ModelSize.Micro)
)
Log.d("Cancel", "Cancelled: $cancelled")

      

Loading & Unloading Models

Load Model

Load a downloaded model into memory:

        lifecycleScope.launch {
    try {
        modelManager.loadModel(
            module = OCRModule.ShippingLabel(ModelSize.Micro),
            apiKey = apiKey,
            executionProvider = ExecutionProvider.NNAPI  // Hardware acceleration
        )
        Log.d("Load", "Model loaded!")
    } catch (e: ModelException.ModelNotFoundException) {
        Log.e("Load", "Model not downloaded")
    } catch (e: ModelException.LoadException) {
        Log.e("Load", "Failed to load: ${e.cause?.message}")
    }
}

      

Check if Model is Loaded

        val isLoaded = modelManager.isModelLoaded(
    OCRModule.ShippingLabel(ModelSize.Micro)
)
Log.d("Status", "Model loaded: $isLoaded")

      

Unload Model

Free memory by unloading a model (file remains on disk):

        val unloaded = modelManager.unloadModel(
    OCRModule.ShippingLabel(ModelSize.Micro)
)
Log.d("Unload", "Unloaded: $unloaded")

      

Get Loaded Model Count

        val loadedCount = modelManager.loadedModelCount()
Log.d("Status", "Models in memory: $loadedCount")

      

Querying Models

Find Downloaded Models

Get all downloaded models:

        lifecycleScope.launch {
    val downloadedModels = modelManager.findDownloadedModels()
    
    downloadedModels.forEach { model ->
        Log.d("Models", """
            Module: ${model.module}
            Version: ${model.version}
            Loaded: ${model.isLoaded}
        """.trimIndent())
    }
}

      

Find Specific Model

        lifecycleScope.launch {
    val modelInfo = modelManager.findDownloadedModel(
        OCRModule.ShippingLabel(ModelSize.Micro)
    )
    
    if (modelInfo != null) {
        Log.d("Model", "Version: ${modelInfo.version}")
        Log.d("Model", "Loaded: ${modelInfo.isLoaded}")
    } else {
        Log.d("Model", "Not downloaded")
    }
}

      

Get Model Version

Returns the version string of a specific downloaded model.

Useful for debugging, logging, and feature validation.

        // Signature
suspend fun getModelVersion(module: OCRModule): String?

      

Example:

        lifecycleScope.launch {
    val module = OCRModule.ShippingLabel(ModelSize.Micro)
    val version = modelManager.getModelVersion(module)
    
    if (version != null) {
        Log.d("Model", "ShippingLabel Micro version: $version")
        
        // Use version for debugging or feature gating
        if (version >= "2025-05-01") {
            // New features available
        }
    } else {
        Log.d("Model", "Model not downloaded")
    }
}

      

Sample Output (if downloaded):

        ShippingLabel Micro version: 2025-05-05

      

Sample Output (if not downloaded):

        Model not downloaded

      

Common Use Cases:

        // 1. Debugging - Log all model versions
lifecycleScope.launch {
    val modules = listOf(
        OCRModule.ShippingLabel(ModelSize.Micro),
        OCRModule.BillOfLading(ModelSize.Micro),
        OCRModule.ItemLabel(ModelSize.Micro)
    )
    
    modules.forEach { module ->
        val version = modelManager.getModelVersion(module)
        Log.d("ModelVersions", "${module.modelClass}: ${version ?: "not downloaded"}")
    }
}

// 2. Feature Validation - Check if model supports new features
lifecycleScope.launch {
    val module = OCRModule.ShippingLabel(ModelSize.Micro)
    val version = modelManager.getModelVersion(module)
    
    val supportsNewFeature = version != null && version >= "2025-05-01"
    if (supportsNewFeature) {
        // Use new OCR features
    }
}

// 3. Telemetry - Report model versions to analytics
lifecycleScope.launch {
    val modules = modelManager.findLoadedModels()
    modules.forEach { modelInfo ->
        analytics.logEvent("model_loaded", mapOf(
            "model_class" to modelInfo.module.modelClass.name,
            "model_size" to modelInfo.module.modelSize?.name,
            "version" to modelInfo.version
        ))
    }
}

      

Returns: String? (version string like "2025-05-05", or null if not downloaded)

Throws: IllegalArgumentException if modelSize is null

Find Loaded Models

Get all models currently in memory:

        lifecycleScope.launch {
    val loadedModels = modelManager.findLoadedModels()
    
    Log.d("Loaded", "=== ${loadedModels.size} models in memory ===")
    loadedModels.forEach { model ->
        Log.d("Loaded", "${model.module}")
    }
}

      

Checking for Updates

Check if Update Available

        lifecycleScope.launch {
    try {
        val updateInfo = modelManager.checkModelUpdates(
            module = OCRModule.ShippingLabel(ModelSize.Micro),
            apiKey = apiKey
        )
        
        if (updateInfo.updateAvailable) {
            Log.d("Update", "Update available!")
            Log.d("Update", "Current: ${updateInfo.currentVersion}")
            Log.d("Update", "Latest: ${updateInfo.latestVersion}")
            Log.d("Update", updateInfo.message)
        } else {
            Log.d("Update", "Already up to date")
        }
    } catch (e: ModelException.NetworkException) {
        Log.e("Update", "Network error: ${e.message}")
    }
}

      

Download Update

        lifecycleScope.launch {
    val module = OCRModule.ShippingLabel(ModelSize.Micro)
    val updateInfo = modelManager.checkModelUpdates(module, apiKey)
    
    if (updateInfo.updateAvailable) {
        // Delete old version
        modelManager.deleteModel(module)
        
        // Download new version
        modelManager.downloadModel(module, apiKey) { progress ->
            progressBar.progress = (progress.progress * 100).toInt()
        }
        
        // Load new version
        modelManager.loadModel(module, apiKey)
        
        Toast.makeText(context, "Model updated!", Toast.LENGTH_SHORT).show()
    }
}

      

Deleting Models

Delete Model

Remove a model from disk (unloads first if loaded):

        lifecycleScope.launch {
    val deleted = modelManager.deleteModel(
        OCRModule.ShippingLabel(ModelSize.Micro)
    )
    Log.d("Delete", "Deleted: $deleted")
}

      

Using Models for OCR

Dynamic OCR with Explicit Module

Use loaded models for on-device OCR:

        // 1. Create OCR manager
val ocrManager = OnDeviceOCRManager(
    context = context,
    ocrModule = OCRModule.ShippingLabel(ModelSize.Micro)
)

// 2. Ensure model is loaded
lifecycleScope.launch {
    if (!modelManager.isModelLoaded(OCRModule.ShippingLabel(ModelSize.Large))) {
        modelManager.loadModel(
            module = OCRModule.ShippingLabel(ModelSize.Large),
            apiKey = apiKey
        )
    }
    
    // 3. Perform OCR with explicit module
    val result = ocrManager.makePrediction(
        ocrModule = OCRModule.ShippingLabel(ModelSize.Large),
        bitmap = capturedBitmap,
        scannedCodeResults = detectedBarcodes
    )
    
    Log.d("OCR", "Result: $result")
}

      

Switch Between Models

        lifecycleScope.launch {
    // Load multiple models
    modelManager.loadModel(OCRModule.ShippingLabel(ModelSize.Micro), apiKey)
    modelManager.loadModel(OCRModule.ShippingLabel(ModelSize.Large), apiKey)
    
    // Use Micro for preview (fast)
    val previewResult = ocrManager.makePrediction(
        ocrModule = OCRModule.ShippingLabel(ModelSize.Micro),
        bitmap = previewBitmap,
        scannedCodeResults = emptyList()
    )
    
    // Use Large for final scan (accurate)
    val finalResult = ocrManager.makePrediction(
        ocrModule = OCRModule.ShippingLabel(ModelSize.Large),
        bitmap = finalBitmap,
        scannedCodeResults = barcodes
    )
}

      

Lifecycle Callbacks

Implement Listener

Monitor model lifecycle events:

        class MyLifecycleListener(
    private val context: Context,
    private val onStateChanged: () -> Unit
) : ModelLifecycleListener {
    
    override fun onDownloadStarted(module: OCRModule) {
        Log.d("Lifecycle", "Download started: $module")
    }
    
    override fun onDownloadCompleted(module: OCRModule) {
        Log.d("Lifecycle", "Download completed: $module")
        (context as? Activity)?.runOnUiThread {
            Toast.makeText(context, "Downloaded!", Toast.LENGTH_SHORT).show()
            onStateChanged()
        }
    }
    
    override fun onDownloadFailed(module: OCRModule, exception: ModelException) {
        Log.e("Lifecycle", "Download failed: $module", exception)
        (context as? Activity)?.runOnUiThread {
            Toast.makeText(context, "Download failed", Toast.LENGTH_LONG).show()
        }
    }
    
    override fun onDownloadCancelled(module: OCRModule) {
        Log.d("Lifecycle", "Download cancelled: $module")
    }
    
    override fun onModelLoaded(module: OCRModule) {
        Log.d("Lifecycle", "Model loaded: $module")
        (context as? Activity)?.runOnUiThread {
            onStateChanged()
        }
    }
    
    override fun onModelUnloaded(module: OCRModule) {
        Log.d("Lifecycle", "Model unloaded: $module")
    }
    
    override fun onModelDeleted(module: OCRModule) {
        Log.d("Lifecycle", "Model deleted: $module")
        (context as? Activity)?.runOnUiThread {
            onStateChanged()
        }
    }
}

      

Register Listener

        ModelManager.initialize(applicationContext) {
    lifecycleListener(MyLifecycleListener(context) {
        updateUI()
    })
}

      

Error Handling

Handle All Exceptions

        lifecycleScope.launch {
    try {
        modelManager.downloadModel(module, apiKey)
        modelManager.loadModel(module, apiKey)
        
    } catch (e: ModelException) {
        when (e) {
            is ModelException.SdkNotInitializedException -> {
                showError("Please restart the app")
            }
            is ModelException.RootedDeviceException -> {
                showError("Cannot run on rooted device")
                finish()
            }
            is ModelException.NoNetworkException -> {
                showError("No internet connection")
            }
            is ModelException.NetworkException -> {
                showError("Network error: ${e.cause?.message}")
            }
            is ModelException.StorageException -> {
                val neededMB = (e.requiredBytes - e.availableBytes) / 1_000_000
                showError("Need ${neededMB}MB more storage")
            }
            is ModelException.ModelNotFoundException -> {
                showError("Model not available: ${e.reason}")
            }
            is ModelException.LoadException -> {
                showError("Failed to load model")
            }
        }
    }
}

      

Exception Types

Exception When How to Handle
SdkNotInitializedException SDK not initialized Call VisionSDK.configure()
RootedDeviceException Device is rooted Exit app (security violation)
NoNetworkException No internet Show offline mode
NetworkException Network error Show retry dialog
StorageException Insufficient storage Ask user to free space
ModelNotFoundException Model not downloaded Download model first
LoadException Failed to load Try CPU fallback or re-download

Memory Management

Unload Models on Low Memory

        override fun onTrimMemory(level: Int) {
    super.onTrimMemory(level)
    
    val modelManager = ModelManager.getInstance()
    
    when (level) {
        TRIM_MEMORY_RUNNING_LOW -> {
            // Unload least critical models
            modelManager.unloadModel(
                OCRModule.DocumentClassification(ModelSize.Micro)
            )
        }
        TRIM_MEMORY_RUNNING_CRITICAL -> {
            // Unload all models
            lifecycleScope.launch {
                modelManager.findLoadedModels().forEach { modelInfo ->
                    modelManager.unloadModel(modelInfo.module)
                }
            }
        }
    }
}

      

Load Models Just-In-Time

        suspend fun performOCR(bitmap: Bitmap): String {
    val module = OCRModule.ShippingLabel(ModelSize.Micro)
    val modelManager = ModelManager.getInstance()
    
    // Ensure model is loaded
    if (!modelManager.isModelLoaded(module)) {
        val modelInfo = modelManager.findDownloadedModel(module)
        
        if (modelInfo == null) {
            // Download first
            modelManager.downloadModel(module, apiKey)
        }
        
        // Load into memory
        modelManager.loadModel(module, apiKey)
    }
    
    // Perform OCR
    return ocrManager.makePrediction(
        ocrModule = module,
        bitmap = bitmap,
        scannedCodeResults = emptyList()
    )
}

      

Complete Examples

Onboarding Flow

Download all required models during app onboarding:

        class OnboardingActivity : AppCompatActivity() {
    
    private val requiredModels = listOf(
        OCRModule.ShippingLabel(ModelSize.Micro),
        OCRModule.DocumentClassification(ModelSize.Micro)
    )
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_onboarding)
        
        lifecycleScope.launch {
            downloadAllModels()
        }
    }
    
    private suspend fun downloadAllModels() {
        val modelManager = ModelManager.getInstance()
        
        requiredModels.forEachIndexed { index, module ->
            try {
                binding.statusText.text = "Downloading ${index + 1}/${requiredModels.size}"
                binding.moduleText.text = module.toString()
                
                modelManager.downloadModel(
                    module = module,
                    apiKey = apiKey,
                    progressListener = { progress ->
                        runOnUiThread {
                            binding.progressBar.progress = (progress.progress * 100).toInt()
                            binding.percentText.text = "${(progress.progress * 100).toInt()}%"
                        }
                    }
                )
            } catch (e: ModelException) {
                handleError(module, e)
                return
            }
        }
        
        // All models downloaded
        Toast.makeText(this, "Setup complete!", Toast.LENGTH_SHORT).show()
        navigateToMainActivity()
    }
    
    private fun handleError(module: OCRModule, exception: ModelException) {
        val message = when (exception) {
            is ModelException.NetworkException -> "Network error. Please check your connection."
            is ModelException.StorageException -> "Not enough storage space."
            else -> "Download failed: ${exception.message}"
        }
        
        AlertDialog.Builder(this)
            .setTitle("Download Failed")
            .setMessage(message)
            .setPositiveButton("Retry") { _, _ ->
                lifecycleScope.launch {
                    downloadAllModels()
                }
            }
            .setNegativeButton("Exit") { _, _ ->
                finish()
            }
            .show()
    }
}

      

Update Check Flow

Check and download updates:

        class SettingsActivity : AppCompatActivity() {
    
    private val modelManager = ModelManager.getInstance()
    
    fun checkForUpdates() {
        lifecycleScope.launch {
            val modules = modelManager.findDownloadedModels()
            
            val updatesAvailable = mutableListOf<Pair<OCRModule, ModelUpdateInfo>>()
            
            modules.forEach { modelInfo ->
                try {
                    val updateInfo = modelManager.checkModelUpdates(
                        module = modelInfo.module,
                        apiKey = apiKey
                    )
                    
                    if (updateInfo.updateAvailable) {
                        updatesAvailable.add(modelInfo.module to updateInfo)
                    }
                } catch (e: ModelException.NetworkException) {
                    Log.e("Update", "Failed to check updates for ${modelInfo.module}")
                }
            }
            
            if (updatesAvailable.isNotEmpty()) {
                showUpdateDialog(updatesAvailable)
            } else {
                Toast.makeText(
                    this@SettingsActivity,
                    "All models are up to date",
                    Toast.LENGTH_SHORT
                ).show()
            }
        }
    }
    
    private fun showUpdateDialog(updates: List<Pair<OCRModule, ModelUpdateInfo>>) {
        val message = buildString {
            append("${updates.size} model update(s) available:\n\n")
            updates.forEach { (module, info) ->
                append("• ${module::class.simpleName}\n")
                append("  ${info.currentVersion} → ${info.latestVersion}\n\n")
            }
        }
        
        AlertDialog.Builder(this)
            .setTitle("Updates Available")
            .setMessage(message)
            .setPositiveButton("Update All") { _, _ ->
                updateAllModels(updates.map { it.first })
            }
            .setNegativeButton("Later", null)
            .show()
    }
    
    private fun updateAllModels(modules: List<OCRModule>) {
        lifecycleScope.launch {
            modules.forEachIndexed { index, module ->
                try {
                    // Delete old version
                    modelManager.deleteModel(module)
                    
                    // Download new version
                    modelManager.downloadModel(
                        module = module,
                        apiKey = apiKey,
                        progressListener = { progress ->
                            runOnUiThread {
                                updateProgressUI(index, modules.size, progress.progress)
                            }
                        }
                    )
                    
                    // Load new version
                    modelManager.loadModel(module, apiKey)
                    
                } catch (e: ModelException) {
                    Log.e("Update", "Failed to update $module: ${e.message}")
                }
            }
            
            Toast.makeText(this@SettingsActivity, "Models updated!", Toast.LENGTH_SHORT).show()
        }
    }
}

      

Model List UI

Display all downloaded and loaded models:

        class ModelsActivity : AppCompatActivity() {
    
    private val modelManager = ModelManager.getInstance()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_models)
        
        loadModelList()
    }
    
    private fun loadModelList() {
        lifecycleScope.launch {
            val downloadedModels = modelManager.findDownloadedModels()
            
            val modelItems = downloadedModels.map { modelInfo ->
                ModelItem(
                    module = modelInfo.module,
                    version = modelInfo.version,
                    isLoaded = modelInfo.isLoaded,
                    onLoadClick = { loadModel(modelInfo.module) },
                    onUnloadClick = { unloadModel(modelInfo.module) },
                    onDeleteClick = { deleteModel(modelInfo.module) }
                )
            }
            
            binding.recyclerView.adapter = ModelListAdapter(modelItems)
        }
    }
    
    private fun loadModel(module: OCRModule) {
        lifecycleScope.launch {
            try {
                modelManager.loadModel(module, apiKey)
                Toast.makeText(this@ModelsActivity, "Model loaded", Toast.LENGTH_SHORT).show()
                loadModelList() // Refresh
            } catch (e: ModelException) {
                Toast.makeText(this@ModelsActivity, "Failed to load", Toast.LENGTH_SHORT).show()
            }
        }
    }
    
    private fun unloadModel(module: OCRModule) {
        val unloaded = modelManager.unloadModel(module)
        if (unloaded) {
            Toast.makeText(this, "Model unloaded", Toast.LENGTH_SHORT).show()
            loadModelList() // Refresh
        }
    }
    
    private fun deleteModel(module: OCRModule) {
        AlertDialog.Builder(this)
            .setTitle("Delete Model")
            .setMessage("Are you sure you want to delete this model? You'll need to download it again to use it.")
            .setPositiveButton("Delete") { _, _ ->
                lifecycleScope.launch {
                    modelManager.deleteModel(module)
                    Toast.makeText(this@ModelsActivity, "Model deleted", Toast.LENGTH_SHORT).show()
                    loadModelList() // Refresh
                }
            }
            .setNegativeButton("Cancel", null)
            .show()
    }
}

      

Best Practices

1. Initialize Early

Initialize ModelManager in your Application class:

        class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        VisionSDK.configure(this, apiKey, Environment.PRODUCTION)
        ModelManager.initialize(this)
    }
}

      

2. Pre-download During Onboarding

Download models during setup, not during first use:

        // ✅ Good: Download during onboarding
class OnboardingActivity {
    fun setupApp() {
        lifecycleScope.launch {
            modelManager.downloadModel(module, apiKey)
        }
    }
}

// ❌ Bad: Download during scanning
class ScanActivity {
    fun startScan() {
        lifecycleScope.launch {
            modelManager.downloadModel(module, apiKey) // User waits!
            scan()
        }
    }
}

      

3. Load Models Just-In-Time

Load models when needed, unload when done:

        // Load before use
suspend fun scan() {
    if (!modelManager.isModelLoaded(module)) {
        modelManager.loadModel(module, apiKey)
    }
    // Use model...
}

// Unload when done
override fun onPause() {
    super.onPause()
    modelManager.unloadModel(module)
}

      

4. Handle Errors Gracefully

Always handle ModelException:

        try {
    modelManager.downloadModel(module, apiKey)
} catch (e: ModelException) {
    when (e) {
        is ModelException.NetworkException -> showRetryDialog()
        is ModelException.StorageException -> showStorageWarning()
        else -> showGenericError()
    }
}

      

5. Check Updates Periodically

Check for updates on app start or in settings:

        lifecycleScope.launch {
    val updateInfo = modelManager.checkModelUpdates(module, apiKey)
    if (updateInfo.updateAvailable) {
        showUpdatePrompt()
    }
}

      

6. Use Lifecycle Callbacks

Monitor model operations for better UX:

        ModelManager.initialize(context) {
    lifecycleListener(object : ModelLifecycleListener {
        override fun onDownloadCompleted(module: OCRModule) {
            updateUI()
        }
    })
}

      

API Reference

For complete API documentation including all methods, parameters, and return types, see the Model Management API Reference.

Quick Reference

Initialization:

  • ModelManager.initialize() - Initialize singleton
  • ModelManager.getInstance() - Get singleton instance
  • ModelManager.isInitialized() - Check initialization status

Download:

  • downloadModel() - Download model
  • cancelDownload() - Cancel download
  • activeDownloadCount() - Get active download count

Load/Unload:

  • loadModel() - Load model into memory
  • unloadModel() - Unload model from memory
  • isModelLoaded() - Check if loaded
  • loadedModelCount() - Get loaded count

Query:

  • findDownloadedModels() - Get all downloaded models
  • findDownloadedModel() - Get specific model
  • findLoadedModels() - Get all loaded models
  • checkModelUpdates() - Check for updates

Delete:

  • deleteModel() - Delete model from disk

OCR:

  • makePrediction() - Perform OCR with loaded model

Thread Safety

All suspend functions are main-safe—they handle threading internally:

  • ✅ Safe to call from main thread
  • ✅ Safe to call from background thread
  • ✅ Handle IO operations internally
  • ✅ Return results on calling thread

Non-suspending functions are thread-safe and non-blocking:

  • isModelLoaded() - O(1), thread-safe
  • unloadModel() - Thread-safe
  • cancelDownload() - Thread-safe
  • activeDownloadCount() - Thread-safe
  • loadedModelCount() - Thread-safe

Troubleshooting

Model Not Loading

Problem: ModelNotFoundException when calling loadModel()

Solution: Download the model first:

        lifecycleScope.launch {
    // Check if downloaded
    val modelInfo = modelManager.findDownloadedModel(module)
    if (modelInfo == null) {
        // Download first
        modelManager.downloadModel(module, apiKey)
    }
    // Now load
    modelManager.loadModel(module, apiKey)
}

      

Download Fails

Problem: NetworkException during download

Solutions:

  1. Check internet connection
  2. Verify API key is valid
  3. Check device has enough storage
  4. Try again with retry logic
        suspend fun downloadWithRetry(module: OCRModule, maxRetries: Int = 3) {
    repeat(maxRetries) { attempt ->
        try {
            modelManager.downloadModel(module, apiKey)
            return // Success
        } catch (e: ModelException.NetworkException) {
            if (attempt == maxRetries - 1) throw e
            delay(2000) // Wait before retry
        }
    }
}

      

NNAPI Load Failure

Problem: LoadException with NNAPI

Solution: Fallback to CPU:

        try {
    modelManager.loadModel(module, apiKey, ExecutionProvider.NNAPI)
} catch (e: ModelException.LoadException) {
    // Try CPU fallback
    modelManager.loadModel(module, apiKey, ExecutionProvider.CPU)
}

      

Out of Memory

Problem: App crashes with OOM

Solution: Unload unused models:

        override fun onTrimMemory(level: Int) {
    super.onTrimMemory(level)
    if (level >= TRIM_MEMORY_RUNNING_LOW) {
        lifecycleScope.launch {
            modelManager.findLoadedModels().forEach {
                modelManager.unloadModel(it.module)
            }
        }
    }
}

      

Migration Guide

From Legacy configure() to ModelManager

Old approach:

        val ocrManager = OnDeviceOCRManager(context, module)
ocrManager.configure(apiKey) { progress ->
    // Update progress
}
val result = ocrManager.makePrediction(bitmap, barcodes)

      

New approach:

        // Download and load separately
modelManager.downloadModel(module, apiKey) { progress ->
    // Update progress
}
modelManager.loadModel(module, apiKey)

// Use with explicit module
val result = ocrManager.makePrediction(
    ocrModule = module,
    bitmap = bitmap,
    scannedCodeResults = barcodes
)

      

Benefits:

  • Download during onboarding, not first use
  • Query model status before operations
  • Manage multiple models independently
  • Better error handling
  • Update models without re-initialization