CameraKStateHolder API¶
Core state management for CameraK. Manages camera lifecycle, exposes reactive state, and handles plugin coordination.
Overview¶
CameraKStateHolder is the primary class for camera operations. It manages:
- Camera lifecycle (initialization -> ready -> cleanup)
- Reactive state via StateFlow
- Plugin lifecycle and coordination
- One-shot events via SharedFlow
Creation¶
@Composable
expect fun rememberCameraKState(
config: CameraConfiguration = CameraConfiguration(),
setupPlugins: suspend (CameraKStateHolder) -> Unit = {},
): State<CameraKState>
Parameters:
- config -- Camera configuration data class with defaults
- setupPlugins -- Suspend function to attach plugins to the state holder
Example:
val cameraState by rememberCameraKState(
config = CameraConfiguration(
cameraLens = CameraLens.BACK,
flashMode = FlashMode.AUTO,
aspectRatio = AspectRatio.RATIO_16_9
),
setupPlugins = { stateHolder ->
stateHolder.attachPlugin(QRScannerPlugin())
stateHolder.attachPlugin(OcrPlugin())
}
)
Properties¶
cameraState¶
val cameraState: StateFlow<CameraKState>
Observable camera lifecycle state. Emits:
- CameraKState.Initializing -- Camera starting
- CameraKState.Ready(controller, uiState) -- Camera operational
- CameraKState.Error(exception, message, isRetryable) -- Initialization failed
Usage:
val cameraState by rememberCameraKState()
when (cameraState) {
is CameraKState.Initializing -> CircularProgressIndicator()
is CameraKState.Ready -> {
val ready = cameraState as CameraKState.Ready
CameraPreviewView(controller = ready.controller)
}
is CameraKState.Error -> {
val error = cameraState as CameraKState.Error
Text("Error: ${error.message}")
}
}
uiState¶
val uiState: StateFlow<CameraUIState>
Observable UI properties (zoom level, flash mode, capturing state, etc.)
CameraUIState structure:
data class CameraUIState(
val zoomLevel: Float = 1.0f,
val maxZoom: Float = 1.0f,
val flashMode: FlashMode? = null,
val torchMode: TorchMode? = null,
val cameraLens: CameraLens? = null,
val imageFormat: ImageFormat = ImageFormat.JPEG,
val qualityPrioritization: QualityPrioritization = QualityPrioritization.BALANCED,
val cameraDeviceType: CameraDeviceType = CameraDeviceType.DEFAULT,
val isCapturing: Boolean = false,
val lastError: String? = null,
)
Usage:
val uiState by stateHolder.uiState.collectAsStateWithLifecycle()
Text("Zoom: ${uiState.zoomLevel}x")
Text("Max: ${uiState.maxZoom}x")
events¶
val events: SharedFlow<CameraKEvent>
One-shot events (not persisted). Collect to handle events like capture success/failure.
CameraKEvent types:
sealed class CameraKEvent {
data object None : CameraKEvent()
data class ImageCaptured(val result: ImageCaptureResult) : CameraKEvent()
data class CaptureFailed(val exception: Exception) : CameraKEvent()
data class QRCodeScanned(val qrCode: String) : CameraKEvent()
data class TextRecognized(val text: String) : CameraKEvent()
data class PermissionDenied(val permission: String) : CameraKEvent()
}
Usage:
LaunchedEffect(Unit) {
stateHolder.events.collect { event ->
when (event) {
is CameraKEvent.ImageCaptured -> {
showToast("Photo captured!")
}
is CameraKEvent.CaptureFailed -> {
showToast("Capture failed: ${event.exception.message}")
}
is CameraKEvent.QRCodeScanned -> {
showToast("QR: ${event.qrCode}")
}
is CameraKEvent.TextRecognized -> {
showToast("OCR: ${event.text}")
}
else -> {}
}
}
}
pluginScope¶
val pluginScope: CoroutineScope
CoroutineScope for plugin operations. Automatically cancelled when state holder is disposed.
Methods¶
getController()¶
fun getController(): CameraController?
Get camera controller if available (state is Ready).
Returns: CameraController or null if camera not ready.
Example:
val controller = stateHolder.getController()
if (controller != null) {
controller.setZoom(2.0f)
} else {
println("Camera not ready")
}
getReadyCameraController()¶
suspend fun getReadyCameraController(): CameraController?
Suspends until camera is ready, then returns controller. Use in plugins.
Returns: CameraController or null if error occurs.
Example:
scope.launch {
val controller = stateHolder.getReadyCameraController()
controller?.setFlashMode(FlashMode.ON)
}
captureImage()¶
fun captureImage()
Captures an image and emits CameraKEvent.ImageCaptured or CameraKEvent.CaptureFailed.
Example:
Button(onClick = { stateHolder.captureImage() }) {
Text("Capture")
}
// Listen for result
LaunchedEffect(Unit) {
stateHolder.events.collect { event ->
when (event) {
is CameraKEvent.ImageCaptured -> {
when (val result = event.result) {
is ImageCaptureResult.SuccessWithFile -> {
println("Saved: ${result.filePath}")
}
is ImageCaptureResult.Error -> {
println("Error: ${result.exception.message}")
}
}
}
}
}
}
setZoom()¶
fun setZoom(zoom: Float)
Sets zoom level. Updates uiState.zoomLevel.
Parameters:
- zoom -- Zoom level (1.0 to maxZoom)
Example:
stateHolder.setZoom(2.5f)
toggleFlashMode()¶
fun toggleFlashMode()
Cycles through flash modes: OFF -> ON -> AUTO -> OFF
Example:
IconButton(onClick = { stateHolder.toggleFlashMode() }) {
Icon(Icons.Default.FlashOn, "Toggle Flash")
}
setFlashMode()¶
fun setFlashMode(mode: FlashMode)
Sets flash mode directly.
Example:
stateHolder.setFlashMode(FlashMode.AUTO)
toggleTorchMode()¶
fun toggleTorchMode()
Toggles torch ON / OFF.
Example:
IconButton(onClick = { stateHolder.toggleTorchMode() }) {
Icon(Icons.Default.FlashlightOn, "Toggle Torch")
}
setTorchMode()¶
fun setTorchMode(mode: TorchMode)
Sets torch mode directly.
Example:
stateHolder.setTorchMode(TorchMode.ON)
toggleCameraLens()¶
fun toggleCameraLens()
Switches between front and back cameras.
Example:
IconButton(onClick = { stateHolder.toggleCameraLens() }) {
Icon(Icons.Default.Cameraswitch, "Switch Camera")
}
attachPlugin()¶
fun attachPlugin(plugin: CameraKPlugin)
Attaches a plugin to the state holder. The plugin's onAttach method is called.
detachPlugin()¶
fun detachPlugin(plugin: CameraKPlugin)
Detaches a plugin from the state holder. The plugin's onDetach method is called.
updateUIState()¶
fun updateUIState(update: (CameraUIState) -> CameraUIState)
Updates the UI state using a transformation function.
emitEvent()¶
suspend fun emitEvent(event: CameraKEvent)
Emits an event to the events SharedFlow.
initialize()¶
suspend fun initialize()
Initializes camera. Called automatically by rememberCameraKState. Don't call manually.
shutdown()¶
fun shutdown()
Cleans up resources. Called automatically when composable leaves composition.
Complete Example¶
@Composable
fun CompleteStateHolderExample() {
val scope = rememberCoroutineScope()
val cameraState by rememberCameraKState(
config = CameraConfiguration(
cameraLens = CameraLens.BACK,
flashMode = FlashMode.AUTO,
aspectRatio = AspectRatio.RATIO_16_9
),
setupPlugins = { stateHolder ->
stateHolder.attachPlugin(QRScannerPlugin())
}
)
// Listen for events
val readyState = cameraState as? CameraKState.Ready
LaunchedEffect(readyState) {
readyState ?: return@LaunchedEffect
// Events can be collected from the controller's parent state holder
}
Box(modifier = Modifier.fillMaxSize()) {
when (val state = cameraState) {
is CameraKState.Ready -> {
val controller = state.controller
val uiState = state.uiState
CameraPreviewView(
controller = controller,
modifier = Modifier.fillMaxSize()
)
// UI State display
Column(
modifier = Modifier
.align(Alignment.TopStart)
.padding(16.dp)
) {
Text("Zoom: ${uiState.zoomLevel}x / ${uiState.maxZoom}x")
Text("Flash: ${uiState.flashMode}")
Text("Lens: ${uiState.cameraLens}")
}
// Capture button
FloatingActionButton(
onClick = {
scope.launch {
controller.takePictureToFile()
}
},
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(32.dp)
) {
if (uiState.isCapturing) {
CircularProgressIndicator(color = Color.White, modifier = Modifier.size(24.dp))
} else {
Icon(Icons.Default.CameraAlt, "Capture")
}
}
// Zoom slider
Slider(
value = uiState.zoomLevel,
onValueChange = { controller.setZoom(it) },
valueRange = 1f..uiState.maxZoom,
modifier = Modifier
.align(Alignment.CenterEnd)
.width(200.dp)
.graphicsLayer { rotationZ = 270f }
)
}
is CameraKState.Error -> {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Camera error: ${state.message}")
if (state.isRetryable) {
Button(onClick = { /* retry */ }) {
Text("Retry")
}
}
}
}
CameraKState.Initializing -> {
CircularProgressIndicator()
}
}
}
}
Thread Safety¶
All public methods are thread-safe. State updates are synchronized internally.
Lifecycle¶
- Creation --
rememberCameraKState()creates state holder - Initialization -- Camera hardware initialized
- Ready -- Camera operational, plugins activated
- Cleanup -- Resources released when composable leaves composition
See Also¶
- CameraController API -- Low-level camera operations
- Configuration -- Configuration options
- Camera Capture Guide -- Capture examples