A use case is one job for the camera: Preview feeds the viewfinder, ImageCapture takes photos, ImageAnalysis hands frames to your code (a barcode scanner, ML Kit), and VideoCapture records. Bound together to a LifecycleOwner, they open and close the camera with it. BookNest keeps them in a ViewModel:
class CameraViewModel : ViewModel() {
...
private val preview = Preview.Builder().build().apply {
setSurfaceProvider { request -> _surfaceRequest.value = request } // to the viewfinder
}
val capture: ImageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.build()
private val analysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) // drop stale frames
.build().apply {
setAnalyzer(analysisExecutor) { image ->
...
image.close() // or no further frames arrive
}
}
/** Binds while the caller's coroutine is active, unbinds when it is cancelled. */
suspend fun bind(context: Context, owner: LifecycleOwner) {
val provider = ProcessCameraProvider.awaitInstance(context)
...
provider.unbindAll()
provider.bindToLifecycle(owner, selector, preview, capture, analysis)
try { awaitCancellation() } finally { provider.unbindAll() }
}The preview's surface requests go through a StateFlow to CameraXViewfinder(request, modifier), and a LaunchedEffect calls bind(). The analyzer averages the luminance (Y) plane every 60 frames; forgetting image.close() freezes analysis after one frame. It received 640x480 frames, the default:
D/BookNestCam( 6547): frame 640x480, rotation 90, brightness 230 D/BookNestCam( 6547): frame 640x480, rotation 90, brightness 202