CameraX: Getting Started

Learn how to implement camera features on Android using CameraX library By Tino Balint.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Creating Use Cases: createPreviewUseCase

Add the following code below startCamera:

  private fun createPreviewUseCase(): Preview {
    // 1
    val previewConfig = PreviewConfig.Builder().apply {
      // 2
      setLensFacing(lensFacing)

      // 3
      setTargetRotation(previewView.display.rotation)
    }.build()

    return Preview(previewConfig)
  }

Here’s what you’re doing in the method above:

  1. Create a configuration for the preview using the PreviewConfig.Builder helper class provided by CameraX.
  2. Set the direction the camera faces using the lensFacing property, which defaults to the rear camera.
  3. Set the target rotation for the preview using the orientation from TextureView.

Creating Use Cases: updateTransform

Go back to createPreviewUseCase and add the following code below it:

  private fun updateTransform() {
    val matrix = Matrix()

    // 1
    val centerX = previewView.width / 2f
    val centerY = previewView.height / 2f

    // 2
    val rotationDegrees = when (previewView.display.rotation) {
      Surface.ROTATION_0 -> 0
      Surface.ROTATION_90 -> 90
      Surface.ROTATION_180 -> 180
      Surface.ROTATION_270 -> 270
      else -> return
    }
    matrix.postRotate(-rotationDegrees.toFloat(), centerX, centerY)

    // 3
    previewView.setTransform(matrix)
  }

In updateTransform, you’re compensating for changes in device orientation. You do this by:

  1. Calculating the center of TextureView.
  2. Correcting the preview output to account for the rotation of the device.
  3. Applying the transformations to TextureView.
Note: In portrait mode, the rotation values are 0 for the standard orientation and 180 when the device is upside-down. In landscape mode, the values are 90 when rotated to the left and 270 when rotated to the right.

Creating Use Cases: createCaptureUseCase

With these two methods in place, you’re ready to add the third missing method: createCaptureUseCase. Add this below updateTransform:

  private fun createCaptureUseCase(): ImageCapture {
    // 2
    val imageCaptureConfig = ImageCaptureConfig.Builder()
         .apply {
           setLensFacing(lensFacing)
           setTargetRotation(previewView.display.rotation)
           // 2
           setCaptureMode(ImageCapture.CaptureMode.MAX_QUALITY)
         }

     return ImageCapture(imageCaptureConfig.build())
  }

Notice how this method is almost identical to the createPreviewUseCase. The only differences are:

  1. You use ImageCaptureConfig.Builder instead of PreviewConfig.Builder.
  2. You set the capture mode to have the max quality.

At this point, you’ve completed the code to start the camera. All you need now is permission to use the camera.

Requesting Permission to Use the Camera

Find requestPermissions in PhotoActivity.kt and replace it with the following code:

  private fun requestPermissions() {
    // 1
    if (allPermissionsGranted()) {
      // 2
      previewView.post { startCamera() }
    } else {
      // 3
      ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS,
          REQUEST_CODE_PERMISSIONS)
    }
  }

Here’s what you’re doing in this method:

  1. Checks if the user granted all permissions
  2. Run previewView.post to make sure TextureView is ready to use. After this, you start the camera.
  3. Request the user’s permission to access the camera.

But what if this is the first time they’re opening your app? To cover that possibility, you’ll replace another method.

Find onRequestPermissionsResult in onCreate and replace it with this:

  override fun onRequestPermissionsResult(
    requestCode: Int, permissions: Array<String>, grantResults: IntArray
  ) {
    if (requestCode == REQUEST_CODE_PERMISSIONS) {
      if (allPermissionsGranted()) {
        previewView.post { startCamera() }
      } else {
        finish()
      }
    }
  }

The above code is similar to the code you added in requestPermissions, except in this case, it happens after the permission has been granted.

Your app is coming along! It’s time to build and run to see your progress.

You’ll see that the app requests two different kinds of permissions before continuing. The first request grants access to the camera:

app screenshot with permission request number one

The second request grants access to the device’s storage, which you’ll need when the user wants to save photos to an external folder:

app screenshot with permission request number two

If you deny either of these requests, your app will immediately close.

After you grant these permissions, you’ll see a camera preview on the bottom half of the screen.

app screenshot with photo of painting in lower half of screen

The preview works fine, but at the moment, you can only use the back camera. There’s a button for toggling the camera in the toolbar, but right now, it doesn’t do anything. You’ll fix that next.

Toggling the Camera Lens

To toggle the camera lens, you’ll need to change the value of lensFacing. You saw this property in the preview and image capture configuration above.

Add this method below createCaptureUseCase:

  private fun toggleFrontBackCamera() {
    lensFacing = if (lensFacing == CameraX.LensFacing.BACK) {
      CameraX.LensFacing.FRONT
    } else {
      CameraX.LensFacing.BACK
    }
    previewView.post { startCamera() }
  }

In this method, you check the current value of lensFacing and swap it. Then you start the camera again with the new lensFacing value.

Now, you’re ready to connect this method with the camera toggle button in the toolbar.

Go back to onCreate and replace setClickListeners with this:

  private fun setClickListeners() {
    toggleCameraLens.setOnClickListener { toggleFrontBackCamera() }
  }

The toggle is all set. Build and run. When you tap on the icon this time, the preview will switch to the front camera. Cool, huh?

Capturing Images

When you added startCamera, you created a use case for image capturing, which provides a way to store the photos. You want to be able to store them in memory for short-term use or in a file for long-term access. You’ll implement that functionality next.

Storing Images to Memory

To take a photo, you’ll make a click listener and a method to trigger it.

Start by adding this below toggleFrontBackCamera in PhotoActivity.kt:

  private fun takePicture() {
    disableActions()
    if (saveImageSwitch.isChecked) {
      savePictureToFile()
    } else {
      savePictureToMemory()
    }
  }

In this method, you first disable all user actions to avoid taking multiple photos at the same time.

Next, you check the value of isChecked for saveImageSwitch. If true, you save the picture to file. If false, you save the picture to memory.

Your next step is to create the methods you referenced in takePicture: savePictureToFile and savePictureToMemory.

Copy this below takePicture:

  // 1
  private fun savePictureToFile() {}

  private fun savePictureToMemory() {
    // 2
    imageCapture?.takePicture(executor,
        object : ImageCapture.OnImageCapturedListener() {
          override fun onError(
              error: ImageCapture.ImageCaptureError,
              message: String, exc: Throwable?
          ) {
            // 3
            Toast.makeText(
              this@PhotoActivity, 
              getString(R.string.image_save_failed),
              Toast.LENGTH_SHORT
            ).show()
          }

          override fun onCaptureSuccess(imageProxy: ImageProxy?,
                                        rotationDegrees: Int) {
            imageProxy?.image?.let {
             // 4
              val bitmap = rotateImage(
                  imageToBitmap(it),
                  rotationDegrees.toFloat()
              )
              
              // 5
              runOnUiThread {
                takenImage.setImageBitmap(bitmap)
                enableActions()
              }
            }
            super.onCaptureSuccess(imageProxy, rotationDegrees)
          }
        })
  }

Here’s what the above code is doing:

  1. savePictureToFile is currently empty. You’ll leave it like that for now.
  2. When you created an image capture use case earlier, you stored it in the imageCapture property. You’ll use that property to call takePicture which takes in two parameters: A listener object for success and error actions, and an executor for running those methods.
  3. If there’s an error, you notify the user with a toast message.
  4. If you captured the image successfully, you’ll convert it to a bitmap. Then, because some devices rotate the image on their own during the image capturing, you rotate it to its original position by calling rotateImage.
  5. Now that the bitmap is ready, you set the image to ImageView on the top half of the screen and re-enable user actions. Note that you switch to a UI thread before you change any UI components by wrapping the code inside the runOnUiThread block.

So now that the user can take a picture, your next step is to let them do so from the preview screen.