Mastering CameraX in Jetpack Compose: A Comprehensive Guide for Android Developers

Deepu George Jacob
8 min readDec 9, 2023

Unlock the power of CameraX in Jetpack Compose with our step-by-step guide tailored for Android developers. In this in-depth blog post, we explore the seamless integration of CameraX into Compose, offering a hands-on tutorial to elevate your app’s camera capabilities.

📸 Key Topics Covered:

  1. Setting Up CameraX and Jetpack Compose: Navigate through the initial setup process, ensuring a smooth integration between CameraX and Jetpack Compose.
  2. Building a Camera Preview in Compose: Dive into the intricacies of creating a dynamic and responsive camera preview interface using Compose’s declarative UI.
  3. Capturing and Processing Images: Learn the art of capturing high-quality images using CameraX and processing them within the Compose framework.

🚀 Why You Should Read:

Whether you’re a seasoned Android developer or a newcomer to Jetpack Compose, this guide equips you with the knowledge and skills needed to harness the full potential of CameraX. Elevate your app’s user experience by seamlessly integrating camera functionalities into your Compose-based projects.

Setting Up CameraX and Jetpack Compose:

To get started, open Android Studio and follow these steps:

  1. Create a New Project:
  1. Click on “New Project,” choose “Empty Activity,” and proceed to the next screen.

2. Name Your Application:

3. Enter the name for your application, such as “CmrXTutorial,” and click “Finish.” Android Studio will automatically download the necessary dependencies.

3. Project Setup Completed:

4. Once the dependencies are downloaded, your project will open in Android Studio.

This visual guide ensures a smooth setup process for integrating CameraX and Jetpack Compose into your Android application.

Opening the AndroidManifest.xml File and Adding Camera Permission:

To configure camera permissions in your Android application, follow these steps:

  1. Open Android Studio and locate your project in the Project Explorer.
  2. Navigate to the app directory and open the src folder.
  3. Inside the src folder, find and open the main folder.
  4. Locate the AndroidManifest.xml file within the main folder and open it.

<!-- Example: app/src/main/AndroidManifest.xml -->

5. Inside the <manifest> tag, add the following permission declaration for camera access:

<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-permission android:name="android.permission.CAMERA"/>

6. Save the changes to the manifest file.

By adding the camera permission to the AndroidManifest.xml file, you ensure that your application has the necessary permissions to access the device’s camera hardware.

Checking Camera Permission in Your Android App:

To ensure a smooth camera integration in your app, follow these steps to check if the necessary camera permission is granted:

1. Open the file where you are implementing camera functionality (e.g., your activity or fragment file).

2. Add the following code to check for camera permission:

 private val cameraPermissionRequest =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
// Implement camera related code
} else {
// Camera permission denied
}

}

3. Where you need to check for camera permission, call the `cameraPermissionRequest`:

when (PackageManager.PERMISSION_GRANTED) {
ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) -> {
// Camera permission already granted
// Implement camera related code
}
else -> {
cameraPermissionRequest.launch(Manifest.permission.CAMERA)
}
}

This code checks if the camera permission is already granted. If not, it requests the permission using the `cameraPermissionRequest` launcher. Adjust the code according to your app’s structure and requirements. My activity looks like this after adding these codes.

package com.app.cmrxtutorial

import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.core.content.ContextCompat
import com.app.cmrxtutorial.ui.theme.CmrXTutorialTheme

class MainActivity : ComponentActivity() {

private val cameraPermissionRequest =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
// Implement camera related code
} else {
// Camera permission denied (Handle denied operation)
}

}


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
when (PackageManager.PERMISSION_GRANTED) {
ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) -> {
// Camera permission already granted
// Implement camera related code
}
else -> {
cameraPermissionRequest.launch(Manifest.permission.CAMERA)
}
}



setContent {
CmrXTutorialTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Greeting("Android")
}
}
}
}
}

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
CmrXTutorialTheme {
Greeting("Android")
}
}

And when I run the code, I am able to see the runtime permission popup on the screen, like the image below. Allow the permission for further processing.

Building a Camera Preview in Compose

To build a camera preview in Jetpack Compose, you’ll need to integrate CameraX with Compose to create a dynamic and responsive UI for displaying the camera feed. Here’s a basic guide:

  1. Add CameraX Dependencies:
    In your app’s build.gradle file, add the CameraX dependencies:
 // Camerax implementation
val cameraxVersion = "1.3.1"
implementation ("androidx.camera:camera-core:${cameraxVersion}")
implementation ("androidx.camera:camera-camera2:${cameraxVersion}")
implementation ("androidx.camera:camera-view:${cameraxVersion}")
implementation ("androidx.camera:camera-lifecycle:$cameraxVersion")
// Camerax implementation

2. Create a Composable for Camera Preview:

Create a new package to add your composable file for the camera, like the image below.

Create file name Camerax.kt

Let’s dive into the code that sets up the camera preview in Jetpack Compose in Camerax.kt file. This Composable function , CameraPreviewScreen(), encapsulates the entire camera preview functionality:

package com.app.cmrxtutorial.composables

import android.content.Context
import androidx.camera.core.CameraSelector
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

@Composable
fun CameraPreviewScreen() {
val lensFacing = CameraSelector.LENS_FACING_BACK
val lifecycleOwner = LocalLifecycleOwner.current
val context = LocalContext.current
val preview = Preview.Builder().build()
val previewView = remember {
PreviewView(context)
}
val cameraxSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
LaunchedEffect(lensFacing) {
val cameraProvider = context.getCameraProvider()
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(lifecycleOwner, cameraxSelector, preview)
preview.setSurfaceProvider(previewView.surfaceProvider)
}
AndroidView(factory = { previewView }, modifier = Modifier.fillMaxSize())
}

private suspend fun Context.getCameraProvider(): ProcessCameraProvider =
suspendCoroutine { continuation ->
ProcessCameraProvider.getInstance(this).also { cameraProvider ->
cameraProvider.addListener({
continuation.resume(cameraProvider.get())
}, ContextCompat.getMainExecutor(this))
}
}

Requesting Camera Permission

Before integrating the camera preview, make sure to handle camera permissions in your app. This step ensures that your app has the necessary permissions to access the device’s camera. You can use the provided code snippet to request camera permission when needed.

Run the Camera Preview Composable

Now that you have the CameraPreviewScreen() Composable, integrate it into your activity or Compose file:

See my MainActivity.kt file

package com.app.cmrxtutorial

import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.core.content.ContextCompat
import com.app.cmrxtutorial.composables.CameraPreviewScreen
import com.app.cmrxtutorial.ui.theme.CmrXTutorialTheme

class MainActivity : ComponentActivity() {

private val cameraPermissionRequest =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
setCameraPreview()
} else {
// Camera permission denied
}

}


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
when (PackageManager.PERMISSION_GRANTED) {
ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) -> {
setCameraPreview()
}
else -> {
cameraPermissionRequest.launch(Manifest.permission.CAMERA)
}
}

}
private fun setCameraPreview() {
setContent {
CmrXTutorialTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
CameraPreviewScreen()
}
}
}
}
}

See the final output from the emulator

Congratulations! You’ve successfully implemented a camera preview in Jetpack Compose using CameraX. This setup provides a foundation for incorporating advanced camera functionalities into your Android app.

Feel free to customize and extend the code based on your app’s requirements. Happy coding!

Capturing and Processing Images:

  1. Enhance the Camera Preview Composable
@Composable
fun CameraPreviewScreen() {
val lensFacing = CameraSelector.LENS_FACING_BACK
val lifecycleOwner = LocalLifecycleOwner.current
val context = LocalContext.current
val preview = Preview.Builder().build()
val previewView = remember {
PreviewView(context)
}
val cameraxSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
val imageCapture = remember {
ImageCapture.Builder().build()
}
LaunchedEffect(lensFacing) {
val cameraProvider = context.getCameraProvider()
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(lifecycleOwner, cameraxSelector, preview, imageCapture)
preview.setSurfaceProvider(previewView.surfaceProvider)
}
Box(contentAlignment = Alignment.BottomCenter, modifier = Modifier.fillMaxSize()) {
AndroidView({ previewView }, modifier = Modifier.fillMaxSize())
Button(onClick = { captureImage(imageCapture, context) }) {
Text(text = "Capture Image")
}
}
}

2. Implement Image Capture Logic.

private fun captureImage(imageCapture: ImageCapture, context: Context) {
val name = "CameraxImage.jpeg"
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, name)
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image")
}
}
val outputOptions = ImageCapture.OutputFileOptions
.Builder(
context.contentResolver,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
contentValues
)
.build()
imageCapture.takePicture(
outputOptions,
ContextCompat.getMainExecutor(context),
object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
println("Successs")
}

override fun onError(exception: ImageCaptureException) {
println("Failed $exception")
}

})
}

You’ve now expanded your camera functionality to capture images in your Jetpack Compose app using CameraX. Customize the code according to your app’s specific image processing requirements. This lays the groundwork for further image processing, analysis, or storage within your application.

Feel free to adapt the code to suit your project’s needs and explore additional features offered by CameraX for image processing and manipulation.Happy coding!

Feel free to check out the code under the repository: https://github.com/DeepuGeorgeJacob/CmrXTutorial/tree/main

— -

Conclusion: Mastering CameraX in Jetpack Compose

Congratulations on successfully navigating the intricacies of integrating CameraX into Jetpack Compose! This series has been a comprehensive guide, emphasizing the strategic use of LaunchedEffect for managing the camera lifecycle.

Next Steps:

As you celebrate this milestone, consider the following avenues for continued growth and refinement:

User-Centric Design: Elevate user interactions by exploring creative UI enhancements and user-centric design principles.

Performance Optimisation: Conduct a thorough evaluation of your camera implementation, focusing on performance optimisations for a smooth and responsive user experience.

Exploration of Camera Effects: Embark on experimentation with additional features such as real-time image processing, filters, or overlays to infuse a touch of uniqueness into your camera app.

Conclusion:

Your adept utilisation of LaunchedEffect to navigate the complex interplay of CameraX and Jetpack Compose showcases a commitment to best practices and efficient code architecture. As you continue your journey in Android development, let curiosity be your guide, and stay attuned to the ever-evolving landscape of the Android ecosystem.

Thank you for embarking on this exploration of CameraX in Jetpack Compose. Your dedication to excellence is evident, and the skills you’ve honed will undoubtedly resonate in your future projects. Happy coding and may your endeavours be filled with continued success! 🚀

--

--