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.
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.
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
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"
}
Add the following permission in your AndroidManifest.xml
:
If you’re using Android 10 or above, you don’t need WRITE_EXTERNAL_STORAGE
to save captured images in cache or scoped storage.
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"
Since CameraX works with a PreviewView (a View
, not a Composable
), we need to embed it using AndroidView
.
@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())
}
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
}
}
}
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
CameraScreen()
}
}
}
}
To capture images, add another use case
val imageCapture = ImageCapture.Builder()
.setTargetRotation(previewView.display.rotation)
.build()
cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
preview,
imageCapture
)
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
.
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
.
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.
M | T | W | T | F | S | S |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 |
We are a team of artists. We provide professional services in the field of Mobile Applications, Web Applications and everything related to IT services. Turning to us for help once – you can no longer refuse.
© 2019 – 2022 | Made with ❤️ by App Ringer
Recent Comments