Skip to content

CameraX Image Capture — Complete Guide

DodaTech Updated 2026-06-24 2 min read

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() on ImageProxy callback.
  • Set setTargetRotation for correct image orientation.
  • Add captured photos to MediaStore for visibility in gallery.

Common Mistakes with camera x image

  1. Non-exhaustive pattern matches that compile with warnings then crash at runtime
  2. Misunderstanding that String is [Char] with poor performance for large text operations
  3. Using foldl instead of foldl' 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

### How do I fix photo rotation?

The ImageCapture uses EXIF orientation when you set setTargetRotation(). For in-memory capture, read EXIF data from the ImageProxy or Bitmap to rotate the image.

### What is the difference between OnImageCapturedCallback and OnImageSavedCallback?

OnImageCapturedCallback returns an ImageProxy (in-memory). OnImageSavedCallback saves directly to a file. Use file-based for permanent storage.

### Why is my captured image blurry?

The focus may not be ready. Use ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY for full resolution. Also check that setTargetResolution is set appropriately.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro