CactusBrain Vision for Android
Add the dependency, configure a project key, prepare a model, and run detection or OCR locally. Version 0.1.0.
Requirements
- minSdk 24 or later
- compileSdk 36
- JDK 21
- An
arm64-v8adevice or emulator image. The native runtime is built and validated for arm64 only; x86_64 is not supported.
1. Add the dependency
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url = uri("https://cactusbrainlabs.com/maven") }
}
}
// app/build.gradle.kts
dependencies {
implementation("ai.cactusbrain:cactusbrain-vision:0.1.0")
}The AAR carries its own native runtime. There is no .so to copy, no JNI to wire up, and no separate engine download.
android {
defaultConfig {
ndk {
// The runtime is built and validated for arm64 only.
abiFilters += "arm64-v8a"
}
}
}Setting abiFilters keeps your APK from advertising an architecture the runtime cannot serve, which otherwise surfaces as an install-time failure on a user device rather than a build-time one on yours.
2. Configure
import ai.cactusbrain.vision.CactusBrainVision
val session = CactusBrainVision.configure(
context = applicationContext,
projectKey = BuildConfig.CACTUSBRAIN_PROJECT_KEY,
)Keep the key out of source control by reading it from local.properties:
// local.properties (gitignored)
cactusbrain.projectKey=cb_live_replace_me
// app/build.gradle.kts
val projectKey: String = gradleLocalProperties(rootDir, providers)
.getProperty("cactusbrain.projectKey", "")
android {
defaultConfig {
buildConfigField("String", "CACTUSBRAIN_PROJECT_KEY", "\"$projectKey\"")
}
}Dashboard → your project → Quickstart. A live key begins cb_live_. It is displayed once.
3. Prepare a model
prepare resolves, downloads, verifies and installs the model your project is entitled to, emitting progress as it goes. It is a no-op once the model is installed.
session.prepare(ModelRequest.Detect).collect { event ->
// Checking, Downloading(received, total), Installing, Ready
update(event)
}4. Detect objects
val output = session.detect(jpegBytes)
output.objects.forEach { obj ->
println("${obj.label} ${obj.confidence} ${obj.boundingBox}")
}Detection takes encoded image bytes, so your app controls the conversion. Apply the EXIF rotation before encoding, or returned coordinates will not line up with the image as the user sees it.
5. Read text
val result = session.ocr(jpegBytes)
println(result.text)
// Or read with a specific model:
val premium = session.ocr(jpegBytes, using = TextModel.Premium)Two OCR models are available: a small fast English model, and a larger multilingual one. Selecting a model does not download it — it arrives when you prepare or run it.
Handling errors
try {
session.prepare(ModelRequest.Detect).collect { update(it) }
} catch (e: ModelDeliveryError.NotEntitled) {
// Retrying will not help. Check entitlements in the dashboard.
showUpgradePath()
} catch (e: ModelDeliveryError.Network) {
showRetry()
} catch (e: ModelDeliveryError.IntegrityCheckFailed) {
// The artifact was discarded rather than installed.
showIntegrityFailure()
} catch (e: ModelDeliveryError.UnsupportedRuntime) {
showUnsupportedDevice()
}Distinguish "not entitled" from "network failed". The first is a permanent answer; the second is worth a retry.
Offline
Once installed, inference runs with no network connection and no per-inference server call. The network is needed to install a model, upgrade it, or install on a new device.