From Setup to Preview: CameraX Integration in Jetpack Compose

Ken Ruiz Inoue
Deuk
Published in
7 min readFeb 27, 2024
Designed with DALL·E 3

In the world of Android development, CameraX stands as Google’s answer to the complexities of the traditional Camera API, offering a more streamlined and accessible approach for developers. Originally designed with XML in mind, it presented a challenge for those venturing into the modern declarative UI toolkit of Jetpack Compose. However, the solution is at hand with the AndroidView() composable, which acts as a bridge, seamlessly integrating CameraX with Compose. This unlocks new possibilities for camera app development in today’s Android ecosystem.

Our goal is simple: to create a camera preview, the core of any camera app, using Android Compose. We’ll explore how to effectively use CameraX with this modern UI framework, turning our vision into reality. Let’s begin!

Environment

  • Android Studio Hedgehog | 2023.1.1 Patch 2
  • Compose version: androidx.compose:compose-bom:2023.08.00
  • Pixel 4 API 28 Emulator

Step 1: Setting the Stage for CameraX in Compose

Before diving into the code, the initial step involves preparing our environment for CameraX integration with Android Compose. This begins with creating an Empty Compose Project. Ensuring your project is ready to handle camera functionalities requires adding specific dependencies to your app’s build.gradle.kt file. Insert the following lines inside the dependencies block.

implementation("androidx.camera:camera-camera2:1.3.1")
implementation("androidx.camera:camera-view:1.3.1")

Since we are just dealing with the preview of the camera (feedback on what the hardware is capturing on the screen), we need only these 2 dependencies:

  • androidx.camera:camera-camera2 is the backbone, providing access to the Camera2 API in a more simplified and manageable manner. This library is essential for camera operations, including the setup and management of the camera session.
  • androidx.camera:camera-view offers a high-level view that handles the camera lifecycle and renders the preview with minimal setup required on our end.

Next, update your project’s AndroidManifest.xml to include the necessary permissions and hardware features. This step is critical for ensuring your application has the ability to access the camera hardware and perform its intended functions without security hitches.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

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

<application
...

Step 2: Navigating Permissions with BaseActivity

Create the BaseActivity.kt class to streamline camera permissions management. This base activity consolidates permission handling, providing a unified and reusable approach across your app. For an in-depth guide on leveraging BaseActivity to simplify permissions management and its application in different projects, explore the resource “Android Camera Permission Essentials: Streamlining with BaseActivity.”


// Your package

import android.Manifest
import android.content.pm.PackageManager
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow

// Open class allowing extension, so activities like MainActivity can inherit from it
// for common camera permission handling functionality
open class BaseActivity : ComponentActivity() {
// Key Point: Managing Camera Permission State
private val _isCameraPermissionGranted = MutableStateFlow(false)
val isCameraPermissionGranted: StateFlow<Boolean> = _isCameraPermissionGranted

// Declare a launcher for the camera permission request, handling the permission result
private val cameraPermissionRequestLauncher: ActivityResultLauncher<String> =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
if (isGranted) {
// Permission granted, update the state
_isCameraPermissionGranted.value = true
} else {
// Permission denied: inform the user to enable it through settings
Toast.makeText(
this,
"Go to settings and enable camera permission to use this feature",
Toast.LENGTH_SHORT
).show()
}
}

// Checks camera permission and either starts the camera directly or requests permission
fun handleCameraPermission() {
when {
ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED -> {
// Permission is already granted, update the state
_isCameraPermissionGranted.value = true
}

else -> {
// Permission is not granted: request it
cameraPermissionRequestLauncher.launch(Manifest.permission.CAMERA)
}
}
}
}

Key Point: Managing Camera Permission State

  • Streamlines Permission Handling: Utilizing MutableStateFlow for the camera permission state simplifies observing and responding to permission changes within your Compose UI, leveraging collectAsState() for reactive updates.
  • Facilitates Compose Integration: This approach aligns with Compose’s reactive nature, automatically allowing UI components to update based on the current permission state.
  • Advocates for Clean Architecture: While BaseActivity serves well for demonstration, adopting a ViewModel for state management in production code fosters better separation of concerns and enhances maintainability.

Step 3: Bringing the Camera to Life with CameraPreview

Creating the CameraPreview composable function is a pivotal step in our journey to craft a fully functional camera application using Android Compose and CameraX. This composable acts as the visual interface, displaying what the camera lens captures in real time. Add the CameraPreview.kt file with the following code.

// Your package

import androidx.camera.view.LifecycleCameraController
import androidx.camera.view.PreviewView
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
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

@Composable
fun CameraPreview() {

// Obtain the current context and lifecycle owner
val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current

// Remember a LifecycleCameraController for this composable
val cameraController = remember {
LifecycleCameraController(context).apply {
// Bind the LifecycleCameraController to the lifecycleOwner
bindToLifecycle(lifecycleOwner)
}
}

// Key Point: Displaying the Camera Preview
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { ctx ->
// Initialize the PreviewView and configure it
PreviewView(ctx).apply {
scaleType = PreviewView.ScaleType.FILL_START
implementationMode = PreviewView.ImplementationMode.COMPATIBLE
controller = cameraController // Set the controller to manage the camera lifecycle
}
},
onRelease = {
// Release the camera controller when the composable is removed from the screen
cameraController.unbind()
}
)
}

Key Point: Displaying the Camera Preview

  • Seamless Integration with Compose: Leveraging AndroidView allows us to incorporate traditional Android views, like PreviewView, into our Compose layout with minimal hassle, bridging the gap between the two UI paradigms.
  • PreviewView: CameraX’s component designed for displaying the camera's live feed, simplifying the integration of real-time camera visuals into an app's interface. It manages the rendering of camera frames, ensuring the live preview is automatically aligned with the application's lifecycle for optimal performance and resource management.
  • Lifecycle Management: Utilizing the LifecycleCameraController is essential for aligning the camera’s operational lifecycle with that of our composable. This synchronization ensures optimal resource utilization and prevents memory leaks, enabling the camera to be precisely controlled within the Compose environment.

Step 4: Merging Components in MainActivity

Finally, proceed to update the MainActivity to integrate all components seamlessly. This step combines the permission handling from BaseActivity with the CameraPreview composable, culminating in a cohesive application flow.

// Your package

import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier

// MainActivity inherits from BaseActivity to utilize its camera permission handling logic
class MainActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// Collect the camera permission state as a Compose state to automatically update the UI upon change
val permissionGranted = isCameraPermissionGranted.collectAsState().value

Box(modifier = Modifier.fillMaxSize()) {
// Conditional UI rendering based on camera permission state
if (permissionGranted) {
// If permission is granted, display the camera preview
CameraPreview()
} else {
// If permission is not granted, display a button to request camera permission
Button(
onClick = {
// Invoke the method from BaseActivity to handle permission request
handleCameraPermission()
},
modifier = Modifier.align(Alignment.Center)
) {
Text(text = "Start Preview")
}
}
}
}
}
}

Upon launching the app and tapping on the Start Preview button, you’ll be prompted with a camera permission dialog. Granting permission activates the CameraPreview(), showcasing the live camera feed directly within the app. This process demonstrates the effective permission management and dynamic rendering capabilities of CameraX in a Compose environment.

Conclusion

That concludes our journey into integrating CameraX with Jetpack Compose for Android development. While blending CameraX’s capabilities with Compose’s declarative UI paradigm presents challenges, the outlined strategies offer effective solutions. The next exciting steps could involve capturing images and managing storage or further exploring CameraX’s advanced features.

For developers eager to deepen their understanding or explore more about modern Android development, a wealth of resources and guides await.

Your thoughts and involvement are truly priceless. If you’ve found this guide helpful, I’d love to hear from you. Consider sharing your support through claps or a follow, and stay tuned for more insights into the evolving landscape of Android development. See you in the next tutorial!

Deuk Services: Your Gateway to Leading Android Innovation

Are you looking to boost your business with top-tier Android solutions?Partner with Deuk services and take your projects to unparalleled heights.

🚀 Boost Your Productivity with Notion

New to Notion? Discover how it can revolutionize your productivity

Ready to take your productivity to the next level? Integrate this content into your Notion workspace with ease:

1 Access the Notion Version of this Content

2 Look for the Duplicate button at the top-right corner of the page

3 Click on it to add this valuable resource to your Notion workspace

Seamlessly integrate this guide into your Notion workspace for easy access and swift reference. Leverage Notion AI to search and extract crucial insights, enhancing your productivity. Start curating your knowledge hub with Notion AI today and maximize every learning moment.

--

--