Software Development

Beginning to Use CameraX in Jetpack Compose – Part – 1

Prince Kumar
10 April 2025
Beginning to Use CameraX in Jetpack Compose – Part – 1

Jetpack Compose has revolutionized Android UI development with its declarative approach. On the other hand, CameraX—a Jetpack support library—simplifies camera integration for Android apps across a wide range of devices. Combine these two powerful tools, and you can create modern, responsive, and camera-driven apps with ease.

In this blog, we’ll walk you through getting started with CameraX in a Jetpack Compose project, step-by-step. Whether you’re a beginner or looking to migrate from XML-based layouts, this guide is your go-to reference.

What is CameraX?

CameraX is a Jetpack support library designed to make camera development easier and more consistent across Android devices. It abstracts away the device-specific quirks and provides consistent APIs for:

  • Previewing camera feed

  • Capturing images

  • Recording videos

  • Analyzing frames in real-time

CameraX is built on top of the Camera2 API, but it’s far easier to use and supports lifecycle-aware components out of the box.

Prerequisites

Before we dive into code, here’s what you need:

Android Studio Setup:

  • Android Studio Hedgehog or later

  • Kotlin 1.8 or later

  • Jetpack Compose enabled

  • minSdkVersion: 21+

  • Camera permissions in manifest

Step 1: Add Dependencies

To begin using CameraX with Jetpack Compose, add the necessary dependencies in your build.gradle (app) file:

				
					dependencies {
    // CameraX core libraries
    def camerax_version = "1.3.0"
    implementation "androidx.camera:camera-core:$camerax_version"
    implementation "androidx.camera:camera-camera2:$camerax_version"
    implementation "androidx.camera:camera-lifecycle:$camerax_version"
    implementation "androidx.camera:camera-view:$camerax_version"
    implementation "androidx.camera:camera-extensions:$camerax_version"

    // Jetpack Compose UI
    implementation "androidx.compose.ui:ui:1.6.0"
    implementation "androidx.compose.material3:material3:1.2.0"
    implementation "androidx.activity:activity-compose:1.8.0"
}

				
			

Also, enable Jetpack Compose in the same file:

				
					buildFeatures {
    compose true
}

composeOptions {
    kotlinCompilerExtensionVersion = "1.6.0"
}

				
			

Step 2: Add Camera Permission

Add the following permission in your AndroidManifest.xml:

				
					<uses-permission android:name="android.permission.CAMERA"/>

				
			

If you’re using Android 10 or above, you don’t need WRITE_EXTERNAL_STORAGE to save captured images in cache or scoped storage.

Step 3: Request Camera Permission at Runtime

Jetpack Compose doesn’t yet have a built-in way to handle permission dialogs, so you can use the Accompanist Permissions library or handle it manually.

				
					@Composable
fun RequestCameraPermission(onGranted: () -> Unit) {
    val context = LocalContext.current
    val permissionState = rememberPermissionState(android.Manifest.permission.CAMERA)

    LaunchedEffect(Unit) {
        permissionState.launchPermissionRequest()
    }

    when {
        permissionState.hasPermission -> onGranted()
        else -> Text("Camera permission is required")
    }
}

				
			

Add Accompanist dependency:

				
					implementation "com.google.accompanist:accompanist-permissions:0.33.0-alpha"
				
			

Step 4: Camera Preview in Compose

Since CameraX works with a PreviewView (a View, not a Composable), we need to embed it using AndroidView.

Create CameraPreview Composable

				
					@Composable
fun CameraPreviewView() {
    val lifecycleOwner = LocalLifecycleOwner.current
    val context = LocalContext.current

    AndroidView(factory = {
        val previewView = PreviewView(it)
        val cameraProviderFuture = ProcessCameraProvider.getInstance(context)

        cameraProviderFuture.addListener({
            val cameraProvider = cameraProviderFuture.get()

            val preview = Preview.Builder().build().also {
                it.setSurfaceProvider(previewView.surfaceProvider)
            }

            val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

            try {
                cameraProvider.unbindAll()
                cameraProvider.bindToLifecycle(
                    lifecycleOwner,
                    cameraSelector,
                    preview
                )
            } catch (e: Exception) {
                Log.e("CameraX", "Use case binding failed", e)
            }

        }, ContextCompat.getMainExecutor(context))

        previewView
    }, modifier = Modifier.fillMaxSize())
}

				
			

Step 5: Putting It All Together

Here’s a screen that shows the camera preview and requests permission:

				
					@Composable
fun CameraScreen() {
    var hasPermission by remember { mutableStateOf(false) }

    if (hasPermission) {
        CameraPreviewView()
    } else {
        RequestCameraPermission {
            hasPermission = true
        }
    }
}
				
			

Use it in MainActivity:

				
					class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MaterialTheme {
                CameraScreen()
            }
        }
    }
}
				
			

Bonus: Capture Image with CameraX

To capture images, add another use case

				
					val imageCapture = ImageCapture.Builder()
    .setTargetRotation(previewView.display.rotation)
    .build()

cameraProvider.bindToLifecycle(
    lifecycleOwner,
    cameraSelector,
    preview,
    imageCapture
)
				
			

And trigger capture like this:

				
					val photoFile = File(context.cacheDir, "photo.jpg")

val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()

imageCapture.takePicture(
    outputOptions,
    ContextCompat.getMainExecutor(context),
    object : ImageCapture.OnImageSavedCallback {
        override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
            Log.d("CameraX", "Photo saved: ${photoFile.absolutePath}")
        }

        override fun onError(exception: ImageCaptureException) {
            Log.e("CameraX", "Photo capture failed: ${exception.message}")
        }
    }
)

				
			

You can expose the imageCapture object from the composable via a remember or pass it via ViewModel.

Best Practices

  • Always check for camera permissions.

  • Handle lifecycle carefully. CameraX is lifecycle-aware, so use bindToLifecycle.

  • Prefer back camera unless you explicitly need the front camera.

  • Use PreviewView inside AndroidView for compatibility.

  • Avoid excessive recompositions inside your CameraPreviewView.

Conclusion

Using CameraX in Jetpack Compose might feel a bit tricky at first due to the use of AndroidView, but once set up, it becomes incredibly powerful. CameraX handles device compatibility, lifecycle, and threading—letting you focus on your app’s functionality.

With this setup, you’re ready to build camera-based apps—whether you’re working on barcode scanners, photo apps, or real-time video processing.

Table of Contents

Recent Comments
    April 2025
    M T W T F S S
     123456
    78910111213
    14151617181920
    21222324252627
    282930  
    Tags:
    androidiOSmobilemobile app development
    6 likes
    Leave a Comment
    Share:
    Social