CameraX + Jetpack Compose: Camera Preview, Capture & QR Code Scanner
CameraX is a Jetpack library that simplifies camera development on Android. Combined with Jetpack Compose, you can build powerful camera features with minimal code.
Setting Up CameraX Dependencies
dependencies {
implementation "androidx.camera:camera-core:1.3.0"
implementation "androidx.camera:camera-camera2:1.3.0"
implementation "androidx.camera:camera-lifecycle:1.3.0"
implementation "androidx.camera:camera-view:1.3.0"
implementation "com.google.mlkit:barcode-scanning:17.2.0"
}
Camera Preview with AndroidView
Integrate CameraX preview into Compose using AndroidView:
val cameraProvider = ProcessCameraProvider.getInstance(context).await()
val preview = Preview.Builder().build().apply {
setSurfaceProvider(surfaceProvider)
}
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
preview
)
AndroidView(
factory = { ctx ->
PreviewView(ctx).apply {
controller = LifecycleCameraController(ctx).apply {
setEnabledUseCases(CameraController.IMAGE_CAPTURE or CameraController.IMAGE_ANALYSIS)
}
}
},
modifier = Modifier.fillMaxSize()
)
Capturing Photos with ImageCapture
Take photos with a single tap:
var capturedUri by remember { mutableStateOf<Uri?>(null) }
val scope = rememberCoroutineScope()
val context = LocalContext.current
val imageCapture = remember {
ImageCapture.Builder()
.setTargetRotation(Surface.ROTATION_0)
.build()
}
Button(onClick = {
scope.launch {
val outputOptions = ImageCapture.OutputFileOptions.Builder(
File(context.cacheDir, "photo_${System.currentTimeMillis()}.jpg")
).build()
imageCapture.takePicture(
outputOptions,
Dispatchers.Main.immediate,
object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
capturedUri = outputFileResults.savedUri
}
override fun onError(exception: ImageCaptureException) {
Log.e("Camera", "Capture failed", exception)
}
}
)
}
}) {
Text("Capture Photo")
}
QR Code Scanner with ML Kit
Scan QR codes in real-time using ML Kit BarcodeScanning:
val imageAnalyzer = ImageAnalysis.Analyzer { imageProxy ->
val mediaImage = imageProxy.image
if (mediaImage != null) {
val image = InputImage.fromMediaImage(
mediaImage,
imageProxy.imageInfo.rotationDegrees
)
val scanner = BarcodeScanning.getClient()
scanner.process(image)
.addOnSuccessListener { barcodes ->
for (barcode in barcodes) {
when (barcode.valueType) {
Barcode.TYPE_URL -> {
Log.d("QR", "Found URL: ${barcode.url?.url}")
}
Barcode.TYPE_TEXT -> {
Log.d("QR", "Found text: ${barcode.rawValue}")
}
}
}
}
.addOnCompleteListener {
imageProxy.close()
}
}
}
ImageAnalysis for Custom Processing
Use ImageAnalysis for real-time frame processing:
val imageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
.also {
it.setAnalyzer(cameraExecutor) { imageProxy ->
val rotationDegrees = imageProxy.imageInfo.rotationDegrees
// Process the frame
imageProxy.close()
}
}
cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
preview,
imageAnalysis,
imageCapture
)
Permission Handling
Request camera permission before using the camera:
val cameraPermissionState = rememberPermissionState(
Manifest.permission.CAMERA
)
LaunchedEffect(Unit) {
cameraPermissionState.launchPermissionRequest()
}
if (cameraPermissionState.status.isGranted) {
// Camera content here
CameraPreview()
} else {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Camera permission is required")
Button(onClick = {
cameraPermissionState.launchPermissionRequest()
}) {
Text("Grant Permission")
}
}
}
Complete Example: QR Scanner Screen
@Composable
fun QRScannerScreen(onQRDetected: (String) -> Unit) {
val cameraPermissionState = rememberPermissionState(Manifest.permission.CAMERA)
var scannedValue by remember { mutableStateOf("") }
LaunchedEffect(Unit) {
cameraPermissionState.launchPermissionRequest()
}
if (cameraPermissionState.status.isGranted) {
Box(modifier = Modifier.fillMaxSize()) {
CameraPreview()
if (scannedValue.isNotEmpty()) {
Card(
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(16.dp)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text("Scanned: $scannedValue")
Button(onClick = {
onQRDetected(scannedValue)
scannedValue = ""
}) {
Text("Use This Code")
}
}
}
}
}
}
}
Best Practices
- Always request camera permission before accessing the camera
- Use
STRATEGY_KEEP_ONLY_LATESTfor real-time processing to prevent frame drops - Close
ImageProxyin analysis callbacks to avoid memory leaks - Bind use cases only when the camera provider is ready
- Test on actual devices as camera behavior varies between emulator and hardware
CameraX with Compose provides a modern, flexible approach to building camera-powered Android apps.
8 Android App Templates → https://myougatheax.gumroad.com
Top comments (0)