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