CameraX - Improving camera usage in your Android App
3 min read

Cameras are one of the most important features of modern apps. However, using the Camera API has always been fairly complex for developers. Unlike the iPhone where Camera hardware is standardized, the Android ecosystem has many different types of hardware, and hence software development around it has been complicated.
Google heard the pain points and as part of their Jetpack set of tools, they released CameraX an SDK that greatly simplifies developing apps with Camera. CameraX supports a wide range of Android devices running Android 5.0 (API level 21) and higher, covering over 98% of devices in use. CameraX focuses on several use cases that allows you to use camera in your app much more easily.
CameraX ensures consistent camera behavior across different devices, handling aspects like aspect ratio, orientation, rotation, preview size, and image size, which traditionally required a lot of manual effort.
CameraX is a Jetpack library, built to help make camera app development easier. For new apps, we recommend starting with CameraX. It provides a consistent, easy-to-use API that works across the vast majority of Android devices, with backward-compatibility to Android 5.0 (API level 21). If you're migrating an app from Camera1, see our Camera1 to CameraX migration guide.
Ease of use
CameraX emphasizes use cases, which allow you to focus on the task you need to get done instead of managing device-specific nuances. Most common camera use cases are supported:
Preview: View an image on the display.
Image analysis: Access a buffer seamlessly for use in your algorithms, such as to pass to ML Kit.
Image capture: Save images.
Video capture: Save video and audio.
Example Code
import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Text
import androidx.compose.runtime.*
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 java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
class MainActivity: ComponentActivity() {
private lateinit var cameraExecutor: ExecutorService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
cameraExecutor = Executors.newSingleThreadExecutor()
setContent {
CameraScreen(cameraExecutor)
}
}
override fun onDestroy() {
super.onDestroy()
cameraExecutor.shutdown()
}
}
@Composable
fun CameraScreen(cameraExecutor: ExecutorService) {
val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
var preview by remember { mutableStateOf<Preview?>(null) }
val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(context) }
Column(modifier = Modifier.fillMaxSize()) {
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { ctx ->
val previewView = PreviewView(ctx)
val executor = ContextCompat.getMainExecutor(ctx)
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
preview = Preview.Builder().build().also {
it.setSurfaceProvider(previewView.surfaceProvider)
}
val imageAnalyzer = ImageAnalysis.Builder()
.build()
.also {
it.setAnalyzer(cameraExecutor, LuminosityAnalyzer { luma ->
Log.d("CameraXBasic", "Average luminosity: $luma")
})
}
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
lifecycleOwner, cameraSelector, preview, imageAnalyzer
)
} catch (e: Exception) {
Log.e("CameraXBasic", "Camera bind failed", e)
}
}, executor)
previewView
}
)
// You can add other Compose UI elements here
Text("Camera Preview")
}
// Check and request camera permissions
RequestCameraPermission()
}
private class LuminosityAnalyzer(private val listener: (luma: Double) -> Unit): ImageAnalysis.Analyzer {
override fun analyze(image: ImageProxy) {
val buffer = image.planes.buffer
val data = buffer.toByteArray()
val pixels = data.map { it.toInt() and 0xFF }
val luma = pixels.average()
listener(luma)
image.close()
}
}
// Permission handling
@Composable
private fun RequestCameraPermission() {
val context = LocalContext.current
if (ContextCompat.checkSelfPermission(
context,
Manifest.permission.CAMERA
)!= PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
context as ComponentActivity,
arrayOf(Manifest.permission.CAMERA),
123 // Request code
)
}
}
fun ByteArray.toByteArray(): ByteArray {
return this
}
If you want to start learning using CameraX please start here.