0Pricing
Android Academy · Lesson

Taking Photos

Capture and save an image.

Taking Photos is a free Android Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Android Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Capturing a Photo

With a preview on screen, the next step is to take a photo. CameRX handles this with the ImageCapture use case.

You'll bind ImageCapture alongside the preview, then call takePicture() when the user taps a shutter button, and save the result to the device gallery.

Building the ImageCapture Use Case

ImageCapture is configured with a builder. A useful option is the capture mode, which trades latency against quality:

  • CAPTURE_MODE_MINIMIZE_LATENCY — fast shutter.
  • CAPTURE_MODE_MAXIMIZE_QUALITY — best image, slightly slower.
val imageCapture = ImageCapture.Builder()
    .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
    .build()

Binding Preview + ImageCapture

You can bind multiple use cases at once. Pass both preview and imageCapture to bindToLifecycle so the camera serves the live feed and is ready to snap a photo.

cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
    lifecycleOwner,
    CameraSelector.DEFAULT_BACK_CAMERA,
    preview,
    imageCapture
)

Where to Save: MediaStore

The modern, scoped-storage-friendly way to save a photo is via MediaStore. You describe the file (name, MIME type, folder) with ContentValues and let the system place it in the shared Pictures collection.

This works without the legacy WRITE_EXTERNAL_STORAGE permission on Android 10+.

val name = "photo_${System.currentTimeMillis()}"
val contentValues = ContentValues().apply {
    put(MediaStore.MediaColumns.DISPLAY_NAME, name)
    put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
    put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/MyApp")
}

Output Options

To tell CameraX where to write the JPEG, build an OutputFileOptions from the content resolver, the target collection URI and your ContentValues.

val outputOptions = ImageCapture.OutputFileOptions
    .Builder(
        context.contentResolver,
        MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
        contentValues
    )
    .build()

Calling takePicture()

Now trigger the capture. takePicture() takes the output options, an Executor and a callback. CameRX reports success or error asynchronously.

Use ContextCompat.getMainExecutor(context) so the callback runs on the main thread.

imageCapture.takePicture(
    outputOptions,
    ContextCompat.getMainExecutor(context),
    object : ImageCapture.OnImageSavedCallback {
        override fun onImageSaved(output: ImageCapture.OutputFileResults) {
            val savedUri = output.savedUri
            // Photo saved! Show a confirmation.
        }
        override fun onError(exc: ImageCaptureException) {
            // Handle the failure.
        }
    }
)

Handling the Result

On success, OutputFileResults.savedUri gives the URI of the saved image. You can display it (with Coil), share it, or show a Snackbar confirming the save.

On error, ImageCaptureException tells you what went wrong — log it and inform the user.

override fun onImageSaved(output: ImageCapture.OutputFileResults) {
    val uri = output.savedUri ?: return
    Toast.makeText(context, "Saved: $uri", Toast.LENGTH_SHORT).show()
}

Wiring a Shutter Button

In Compose, overlay a shutter button on top of your preview using a Box. Hoist the ImageCapture reference so the button's onClick can call takePhoto.

Box(modifier = Modifier.fillMaxSize()) {
    CameraPreview(imageCapture = imageCapture)
    IconButton(
        onClick = { takePhoto(context, imageCapture) },
        modifier = Modifier
            .align(Alignment.BottomCenter)
            .padding(32.dp)
    ) {
        Icon(Icons.Default.PhotoCamera, contentDescription = "Take photo")
    }
}

Extracting takePhoto()

Keep your composable clean by moving the capture logic into a helper. It takes the context and the ImageCapture use case, builds the output options and calls takePicture.

fun takePhoto(context: Context, imageCapture: ImageCapture) {
    val values = ContentValues().apply {
        put(MediaStore.MediaColumns.DISPLAY_NAME, "IMG_${System.currentTimeMillis()}")
        put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
    }
    val options = ImageCapture.OutputFileOptions.Builder(
        context.contentResolver,
        MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
        values
    ).build()

    imageCapture.takePicture(
        options,
        ContextCompat.getMainExecutor(context),
        object : ImageCapture.OnImageSavedCallback {
            override fun onImageSaved(o: ImageCapture.OutputFileResults) {}
            override fun onError(e: ImageCaptureException) {}
        }
    )
}

Flash and Rotation

Two finishing touches:

  • Flash: set imageCapture.flashMode to FLASH_MODE_ON, OFF or AUTO.
  • Rotation: CameRX uses the target rotation to orient the saved JPEG correctly; keep it updated for accurate orientation.
imageCapture.flashMode = ImageCapture.FLASH_MODE_AUTO
// Update target rotation when the device rotates:
imageCapture.targetRotation = display.rotation

Putting It All Together

The full photo flow is: build ImageCapture → bind it with the preview → on shutter tap, build OutputFileOptions and call takePicture → handle onImageSaved / onError.

That's a complete, gallery-saving camera in just a handful of lines.

Quick Check

Confirm you understand the photo-capture flow.

Recap

You can now take and save photos with CameRX:

  • Build an ImageCapture use case and bind it with the preview.
  • Describe the output with ContentValues and MediaStore for scoped storage.
  • Call takePicture() with OutputFileOptions, an executor and an OnImageSavedCallback.
  • Read the savedUri on success; handle ImageCaptureException on error.

Next, you'll record video with the VideoCapture use case.

Frequently asked questions

Is the “Taking Photos” lesson free?

Yes — the full text of “Taking Photos” is free to read here on the web, and the Android Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Android Academy course, upgrade to CoddyKit PRO.

What will I learn in “Taking Photos”?

Capture and save an image. You practise Android Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Android Academy?

No prior experience is required. Android Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Taking Photos” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Android Academy lesson?

Yes. Every Android Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. CameraX Overview
  2. Showing a Camera Preview
  3. Taking Photos
  4. Recording Video
← Back to Android Academy