DEV Community

myougaTheAxo
myougaTheAxo

Posted on

CameraX + Jetpack Compose: Camera Preview, Capture & QR Code Scanner

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"
}
Enter fullscreen mode Exit fullscreen mode

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()
)
Enter fullscreen mode Exit fullscreen mode

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")
}
Enter fullscreen mode Exit fullscreen mode

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()
            }
    }
}
Enter fullscreen mode Exit fullscreen mode

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
)
Enter fullscreen mode Exit fullscreen mode

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")
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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")
                        }
                    }
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Always request camera permission before accessing the camera
  • Use STRATEGY_KEEP_ONLY_LATEST for real-time processing to prevent frame drops
  • Close ImageProxy in 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)