CameraX: Getting Started
Learn how to implement camera features on Android using CameraX library By Tino Balint.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
CameraX: Getting Started
25 mins
- What Is CameraX?
- Getting Started
- Pre-Built Kotlin Classes and Dependencies
- Familiarizing Yourself With the App Screen
- Creating a Camera Preview
- Creating Use Cases: createPreviewUseCase
- Creating Use Cases: updateTransform
- Creating Use Cases: createCaptureUseCase
- Requesting Permission to Use the Camera
- Toggling the Camera Lens
- Capturing Images
- Storing Images to Memory
- Adding Click Listeners
- Storing Images to File
- Adding Vendor Extensions
- Listening for the Image Extension
- Where to Go From Here?
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:
- Create a configuration for the preview using the
PreviewConfig.Builder
helper class provided by CameraX. - Set the direction the camera faces using the lensFacing property, which defaults to the rear camera.
- 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:
- Calculating the center of
TextureView
. - Correcting the preview output to account for the rotation of the device.
- Applying the transformations to
TextureView
.
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:
- You use
ImageCaptureConfig.Builder
instead ofPreviewConfig.Builder
. - 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:
- Checks if the user granted all permissions
- Run
previewView.post
to make sureTextureView
is ready to use. After this, you start the camera. - 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:
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:
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.
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:
-
savePictureToFile
is currently empty. You’ll leave it like that for now. - When you created an image capture use case earlier, you stored it in the
imageCapture
property. You’ll use that property to calltakePicture
which takes in two parameters: A listener object for success and error actions, and an executor for running those methods. - If there’s an error, you notify the user with a toast message.
- 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
. - 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 therunOnUiThread
block.
So now that the user can take a picture, your next step is to let them do so from the preview screen.