Files
VibeCoding/.agents/skills/camerax/references/camera2-to-camerax.md

5.6 KiB

Camera2 offers granular control but introduces boilerplate: managing CameraDevice states, CameraCaptureSession lifecycles, background threads, HandlerThread, and manual orientation calculations.

CameraX simplifies this by binding high-level UseCases such as Preview, ImageCapture, and ImageAnalysis directly to Android lifecycles, handling thread management and device-specific workarounds automatically.


Remove Camera2 boilerplate

Migrating to CameraX removes manual setup code:

  1. Delete manual thread handling : Remove HandlerThread, Handler, and executors dedicated to camera background tasks. CameraX manages its own threads.
  2. Remove session and device callbacks : Delete CameraDevice.StateCallback and CameraCaptureSession.StateCallback implementations.
  3. Remove surface management : Remove manual routing of Surface objects from TextureView or SurfaceView to the camera device.
  4. Remove manual orientation logic : Delete calculations involving CameraCharacteristics.SENSOR_ORIENTATION and display rotation for adjusting preview and capture orientation.

Initialize ProcessCameraProvider

Request the ProcessCameraProvider and bind your use cases to the LifecycleOwner activity or fragment. This replaces the CameraManager.openCamera flow.

val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
LaunchedEffect(context, lifecycleOwner) {
  val cameraProvider = ProcessCameraProvider.getInstance(context).await()

  val cameraSelector = CameraSelector.Builder()
    .requireLensFacing(CameraSelector.LENS_FACING_BACK)
    .build()

  val preview = Preview.Builder().build()
  val imageCapture = ImageCapture.Builder()
    .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
    .build()

  // ImageAnalysis is common when migrating from Camera2 ImageReader
  val imageAnalysis = ImageAnalysis.Builder()
    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
    .build()

  cameraProvider.unbindAll()

  val camera = cameraProvider.bindToLifecycle(
    lifecycleOwner,
    cameraSelector,
    preview,
    imageCapture,
    imageAnalysis
  )
}


Implement the preview and tap-to-focus

CameraX handles surface configuration automatically. Choose based on your UI toolkit:

Option A: For Android Views

Use androidx.camera.view.PreviewView in your layout, and bind it to the Preview use case.

preview.setSurfaceProvider(previewView.surfaceProvider)

Option B: For Jetpack Compose

Use androidx.camera.compose.CameraXViewfinder.

var surfaceRequest by remember { mutableStateOf<SurfaceRequest?>(null) }
val preview = remember {
  Preview.Builder().build().apply {
    setSurfaceProvider { request -> surfaceRequest = request }
  }
}


Capture a photo

Replace ImageReader capture flows and CaptureRequest.Builder.TEMPLATE_STILL_CAPTURE with the ImageCapture use case. CameraX handles the rotation natively using the returned ImageProxy.

imageCapture.takePicture(
  cameraExecutor,
  object : ImageCapture.OnImageCapturedCallback() {
    override fun onCaptureSuccess(image: ImageProxy) {
      val buffer = image.planes[0].buffer
      val bytes = ByteArray(buffer.remaining())
      buffer.get(bytes)
      val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
      if (bitmap != null) {
        val matrix = Matrix()
        matrix.postRotate(image.imageInfo.rotationDegrees.toFloat())
        if (lensFacing == CameraSelector.LENS_FACING_FRONT) {
          matrix.postScale(-1f, 1f)
        }

        val rotatedBitmap = Bitmap.createBitmap(
          bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true
        )
      }

      // MUST close proxy
      image.close()
    }

    override fun onError(exception: ImageCaptureException) {
      Log.e("CameraX", "Capture failed: ${exception.message}", exception)
    }
  }
)


Implement image analysis

If you were using ImageReader in Camera2 to access raw frames, e.g., for QR scanning or ML,, replace it with the CameraX ImageAnalysis use case.

imageAnalysis.setAnalyzer(cameraExecutor) { imageProxy ->
  try {
    val rotationDegrees = imageProxy.imageInfo.rotationDegrees
    // Process image here (e.g., run object detection)
    // ...
  } finally {
    // MUST close the imageProxy to avoid blocking the pipeline
    imageProxy.close()
  }
}


Use Camera2 interop

If your app requires specific Camera2 configuration options, such as custom exposure modes or flash settings, that aren't exposed directly in CameraX, use Camera2Interop to apply them to your CameraX use cases.

// Use Camera2Interop to set Camera2-specific capture options
val extender = Camera2Interop.Extender(imageCaptureBuilder)
extender.setCaptureRequestOption(
  CaptureRequest.CONTROL_AE_MODE,
  CaptureRequest.CONTROL_AE_MODE_OFF
).setCaptureRequestOption(
  CaptureRequest.FLASH_MODE,
  CaptureRequest.FLASH_MODE_TORCH
)


Follow constraints

  • Don't open the CameraDevice manually : Let CameraX manage device opening and closing using bindToLifecycle.
  • Don't forget to close ImageProxy : In both ImageCapture and ImageAnalysis callbacks, you must call image.close(). Failure to do so will block the camera pipeline.
  • Don't block the ImageAnalysis thread : The analyzer runs frame-by-frame. If you need to perform heavy computations, offload them to a background worker and close the ImageProxy as soon as possible.