Capture lifecycle
A capture session is a small state machine you observe through Kotlin flows. Understanding the states makes the UI wiring obvious.
The reader state machine
session.state: StateFlow<ReaderState> drives an exhaustive when:
Ready ──capture starts──▶ Scanning(position) ──frame──▶ Captured(position)
▲ │
│ hardware/transport failure
└──────────────────────── Error(error)Ready— reader idle, no capture in progress.Scanning(position)— a capture forposition(ISO 19794-4, 0..10) is running; the vendor SDK is polling for a quality frame.Captured(position)— a quality-gated image was captured.Error(error)— a hardware/transport failure. A biometric non-match (match/searchreturningmatched = false) is a result, not anError.
The three flows
| Flow | Type | Carries |
|---|---|---|
state | StateFlow<ReaderState> | Latest lifecycle state — render your capture UI from this |
result | SharedFlow<ReaderCaptureResult> | Emitted by capture() — the raw image (base64 PNG + byte count) |
errors | SharedFlow<SladeIDError> | Every handled failure (auth, capture, network) |
Collect them from a coroutine scope tied to your UI (e.g. viewModelScope):
viewModelScope.launch { session.state.collect { render(it) } }
viewModelScope.launch { session.errors.collect { toast(it.message) } }Suspend, off the main thread
Capture, scoring, and submission are suspend functions dispatched to background threads by the SDK — heavy work never runs on the caller’s thread. Call them from a coroutine; they return null on a handled failure (the typed error is emitted on errors) rather than throwing across the API boundary. The only exceptions thrown are programming errors, e.g. an out-of-range position.
One capture at a time
A session serializes captures with an internal mutex, so concurrent calls queue rather than racing the hardware. Create one session per reader and reuse it.