Keep It Simple with Camera Intents in Your Jetpack Compose App

So, you’re looking to add some photo-taking magic to your app without diving too deep into the complex world of Android camera APIs? Don’t waste your time: use Camera Intents. While CameraX is the shiny new toy in the Android camera world, it might be a bit much if you’re just looking to snap a quick pic here and there. That’s where camera intents come into play, and they’re pretty awesome for keeping things simple.

Snap photos the easy way.

Imagine you want your app to take a photo. You don’t need to mess around with the camera hardware yourself. Android has this built-in way to ask the camera app to do the heavy lifting for you, so it’s like saying, “Hey, camera app, mind taking a photo for me?” This is done through something called an intent. It’s basically your app making a request to another app.

val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)

Nicely get permission!

Before your app goes off taking photos, you need to make sure it asks for permission first. You know, manners. Android needs you to get the okay from your users to use the camera. Assuming you’re managing the state in the view model, here’s how you ask for that camera permission.

val getPermissions =
    rememberLauncherForActivityResult(
        contract = ActivityResultContracts.RequestMultiplePermissions(),
        onResult = { permissions ->
          val hasCameraPermissions = permissions[android.Manifest.permission.CAMERA] == true
          viewModel.setCameraPermissions(hasCameraPermissions)
        })

Set up a spot for your photos.

To save those pics without stepping on any toes (or breaking any rules), you’ll need to set up a File Provider. It’s a way to share files securely without needing to ask for extra permissions.

Setting Up a File Provider Looks Like This:

1. Add the File Provider to your manifest.


<?xml version="1.0" encoding="utf-8"?>
<application>
<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${applicationId}.provider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths"></meta-data>
</provider>
</application>

2. Then, make a file_paths.xml in your res/xml directory.


<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-files-path
        name="my_images"
        path="Pictures" />
    <external-files-path
        name="my_files"
        path="Files" />
</paths>

Put your captured image somewhere!

What’s better than to have an extension file to do some of the image creation for us? We will add two functions. One is to create an image file and the other is to get the URI for the file. Now that we have a file provider directory, let’s create an image file where we can store the image we capture from the system camera:


import android.content.Context
import android.net.Uri
import android.os.Environment
import androidx.core.content.FileProvider
import com.company.feature.common.date.getCurrentTimestampFormattedAs // ext using a java Simple Date Format
import java.io.File
import java.util.Locale

fun Context.createImageFile(): File {
  val timeStamp: String = Locale.US.getCurrentTimestampFormattedAs("yyyyMMdd_HHmmss")
  val storageDir: File? = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
  return File.createTempFile("JPEG_${timeStamp}_", ".jpg", storageDir)
}

// optional function if you want to display the image based on the URI
fun Context.getUriForFile(photoFile: File): Uri {
  return FileProvider.getUriForFile(this, "${this.packageName}.fileprovider", photoFile)
}

Let’s define our launcher.


val systemCameraLauncher =
      rememberLauncherForActivityResult(
          contract = ActivityResultContracts.TakePicture(),
          onResult = { success ->
            if (success) {
              viewModel.submitAction(action = PhotoAdded(photos = listOf(capturedPhotoUri.value)))
            }
          })

Trigger away!


val capturedPhotoUri = remember {
    mutableStateOf(context.getUriForFile(context.createImageFile()))
}

val hasPermissions = remember { mutableStateOf(viewModel.state.value.hasCameraPermission) } // Assuming 'hasCameraPermission' is a boolean in your viewModel's state

Button(
    onClick = {
        if (hasPermissions.value) {
            capturedPhotoUri.value = context.getUriForFile(context.createImageFile())
            systemCameraLauncher.launch(capturedPhotoUri.value)
        } else {
            getPermissions.launch(arrayOf(android.Manifest.permission.CAMERA))
        }
    }
) {
    Text(
        modifier = Modifier.fillMaxWidth(),
        text = "Click me",
        textAlign = TextAlign.Center,
        style = MaterialTheme.typography.button // Assuming you're using MaterialTheme
    )
}

 

Note that if your user denies their permission, in the future you should prompt them to turn them on in their settings. That logic is not reflected here for simplicity! The crux of this example is to show you how to set up the launcher and the permissions. This will trigger the correct camera intent and return your app once you take a picture! This is the simplest way to take photos with Jetpack Compose in Android. What if you have a different use case?

CameraX: But I need more!

Ok, so when would you actually use CameraX? Sometimes you need an in-app camera. Let’s say you’re developing an app like Instagram or Snapchat, where users expect to capture, edit, and share photos or videos with various effects. In that case, CameraX is your go-to. It provides the flexibility to implement custom filters, image analysis for augmented reality (AR) effects, or even real-time processing for things like face detection. Another use case is for apps where you need image analysis for example processing images in real-time or allowing edge detection. CameraX provides extensions, broad device compatibility, and even supports an in-app preview gallery.

Conversation

Join the conversation

Your email address will not be published. Required fields are marked *