Android SDK threading model
How the Extentos Android SDK uses Kotlin coroutines and Flow — suspend functions for one-shot ops, Flow/StateFlow for streams, and which dispatcher to collect on.
Camera and display need the Meta vendor module. com.extentos:glasses
carries no vendor SDK, so add implementation("com.extentos:glasses-meta")
alongside it — see install. Without it the build
still succeeds and voice still works, but capabilities.camera is false and
captures return errors. The SDK logs a warning at startup when it spots that
combination.
The Extentos Android SDK is coroutine-native. Its public surface is split cleanly into two shapes:
suspendfunctions for one-shot operations that complete with a result.Flow/StateFlowfor continuous streams and observable state.
There are no callbacks to register and no threads to manage — you collect flows and await suspend functions from your own coroutine scopes.
One-shot operations are suspend
Discrete capability calls suspend until they finish and return an ExtentosResult:
suspend fun takeShot() {
when (val r = glasses.camera.capturePhoto()) {
is ExtentosResult.Ok -> show(r.value)
is ExtentosResult.Err -> report(r.error)
}
glasses.audio.speak("Got it.") // also suspend
}Examples: camera.capturePhoto(), camera.captureVideo(), audio.recordDiscrete(...), audio.speak(...), audio.cancelSpeak(), and glasses.shutdown(). Call them from a coroutine (lifecycleScope, viewModelScope, or any scope you control). They are cooperative with cancellation — cancelling the calling coroutine cancels the operation.
Streams are Flow
Continuous capabilities return a cold Flow you collect for as long as you want the stream:
// Live transcription
glasses.audio.transcriptions().collect { transcript ->
updateCaption(transcript.text)
}
// Raw mic chunks / camera frames
glasses.audio.audioChunks().collect { chunk -> /* ... */ }
glasses.camera.videoFrames().collect { frame -> /* ... */ }These are cold — the underlying capture starts when collection starts and stops when the collecting coroutine is cancelled. Run them on a scope whose lifetime matches the subscription (and start the foreground service if the stream must survive backgrounding).
Observable state is StateFlow / event Flow
Two always-available streams describe the SDK's own state:
| Stream | Type | What it carries |
|---|---|---|
glasses.connection.state | StateFlow<GlassesState> | The live connection state — always has a current value. |
glasses.runtime.events | Flow<RuntimeEvent> | The on-device event log (toggles, transport, logs, warnings). |
Collect connection state lifecycle-aware so the UI tracks it without leaking:
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
glasses.connection.state.collect { state ->
render(state)
}
}
}In Compose, collectAsStateWithLifecycle() is the idiomatic equivalent:
val connState by glasses.connection.state.collectAsStateWithLifecycle()Because state is a StateFlow, a new collector immediately receives the current value — you don't miss the connection state by subscribing late. The event log additionally offers a one-shot snapshot if you don't want to collect: glasses.runtime.snapshotEvents() (a suspend function returning List<RuntimeEvent>).
Dispatcher guidance
- You don't pick a dispatcher for the SDK's internals. It runs its own work on a
SupervisorJob+Dispatchers.Defaultscope created atcreate()and cancelled atshutdown(). Flow emissions and suspend-function bodies execute there. - Collect on a UI-aware scope (
lifecycleScope,viewModelScope) and touch UI from the main dispatcher. UseflowOn(...)only if your own downstream operators are heavy — the SDK already does its capture work off the main thread. - Don't block. Never call a
suspendoperation fromrunBlockingon the main thread; launch it on a coroutine scope.
Don't wrap results in runCatching
Capability and lifecycle operations return ExtentosResult and do not throw for expected failures. Pattern-match the result; runCatching would only ever catch unrelated exceptions and would hide the typed failure. See Errors for the failure variants and the valueOrNull() / map / flatMap helpers.
Related
Initialize the Android SDK
Create ExtentosGlasses with ExtentosGlasses.create, configure ExtentosConfig (environment, applicationContext, transport), reach the sub-clients, and pattern-match ExtentosResult.
Android SDK lifecycle
When to create and shut down ExtentosGlasses, how the bundled foreground service keeps the glasses mic alive in the background, and how Extentos behaves across Activity lifecycle and process death.
Android runtime internals
How the Extentos Android SDK is a thin Kotlin shell over a shared Rust core (uniffi + jniLibs), the four transports and how TransportChoice.Auto resolves, and the Meta DAT bridge.
Error reference
Every typed error the Extentos SDK can return — ConnectError, CaptureError, AudioError, TransportError, the ExtentosError umbrella, and the Meta-DAT DeviceSessionError — with their payload fields and meaning. Lifecycle operations return ExtentosResult<T, E> with these concrete failure variants rather than throwing; pattern-match them. Generated from the Rust core.
Android SDK lifecycle
When to create and shut down ExtentosGlasses, how the bundled foreground service keeps the glasses mic alive in the background, and how Extentos behaves across Activity lifecycle and process death.
Android runtime internals
How the Extentos Android SDK is a thin Kotlin shell over a shared Rust core (uniffi + jniLibs), the four transports and how TransportChoice.Auto resolves, and the Meta DAT bridge.