CameraX Image Capture — Complete Guide
In this tutorial, you'll learn about CameraX Image Capture. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
The Problem
Your CameraX image capture returns a null photo, or the saved image is rotated incorrectly.
Wrong Approach ❌
// Taking photo without output directory
val imageCapture = ImageCapture.Builder()
.build()
imageCapture.takePicture(
ContextCompat.getMainExecutor(this),
object : ImageCapture.OnImageCapturedCallback() {
override fun onCaptureSuccess(image: ImageProxy) {
// ImageProxy must be closed — memory leak!
// image.close() // Forgotten!
}
}
)
Output: Memory leak from unclosed ImageProxy. Photo not saved to file.
Right Approach ✅
class CameraCaptureActivity : AppCompatActivity() {
private var imageCapture: ImageCapture? = null
private fun setupImageCapture() {
imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
.setTargetRotation(windowManager.defaultDisplay.rotation)
.build()
}
fun takePhoto() {
val imageCapture = imageCapture ?: return
// Create output file
val photoFile = File(
getExternalFilesDir(Environment.DIRECTORY_PICTURES),
"photo_${System.currentTimeMillis()}.jpg"
)
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile)
.build()
imageCapture.takePicture(
outputOptions,
ContextCompat.getMainExecutor(this),
object : ImageCapture.OnImageSavedCallback() {
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
Log.d("CameraX", "Photo saved: ${photoFile.absolutePath}")
// Add to MediaStore for visibility
addToGallery(photoFile)
}
override fun onError(exception: ImageCaptureException) {
Log.e("CameraX", "Photo capture failed", exception)
}
}
)
}
// For in-memory capture
fun takePhotoInMemory(callback: (Bitmap) -> Unit) {
imageCapture?.takePicture(
ContextCompat.getMainExecutor(this),
object : ImageCapture.OnImageCapturedCallback() {
override fun onCaptureSuccess(image: ImageProxy) {
val bitmap = imageProxyToBitmap(image)
callback(bitmap)
image.close() // CRITICAL: close to avoid leak
}
override fun onError(exception: ImageCaptureException) {
Log.e("CameraX", "Capture failed", exception)
}
}
)
}
private fun imageProxyToBitmap(image: ImageProxy): Bitmap {
val buffer = image.planes[0].buffer
val bytes = ByteArray(buffer.remaining())
buffer.get(bytes)
return BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
}
private fun addToGallery(file: File) {
val values = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, file.name)
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
}
contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
}
}
Output: Correctly saved photos with proper rotation and cleanup.
Prevention
- Use
OutputFileOptions.<a href="/design-patterns/builder/">Builder</a>with a file for persistent storage. - Always call
image.close()onImageProxycallback. - Set
setTargetRotationfor correct image orientation. - Add captured photos to MediaStore for visibility in gallery.
Common Mistakes with camera x image
- Non-exhaustive pattern matches that compile with warnings then crash at runtime
- Misunderstanding that
Stringis[Char]with poor performance for large text operations - Using
foldlinstead offoldl'causing stack overflow on large lists
These mistakes appear frequently in real-world Android code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.
Practice Exercise
Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.
This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.
FAQ
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro