In this tutorial, we’ll be discussing at length upon Android CameraX API. CameraX was one of the popular releases in Google IO 2019. Let’s see what it has in store for us.
Table of Contents
What is Android CameraX?
Developing Camera Apps has always been hard. Getting a hold of the Camera APIs was never easy.
Hence, CameraX which is introduced in the Jetpack Support Library is a respite. It aims at making the development process easier.
Besides API complexity, while developing camera applications, we had to tackle a lot of scenarios such as:
- OS Versions
- Device Model specifications – Android Market is fragmented and has a vast variety of device configurations. Same Application can behave differently on different phones especially Samsung.
In this regards, CameraX strives to be more consistent across devices. CameraX basically uses Camera2 API under the hood but with a better implementation process. It is backward compatible until Android Lollipop (SDK 21).
How did Google do this?
Using an Automated CameraX test lab with a number of devices from various manufacturers.
CameraX has tackled the following issues as per Google IO 2019.

Source: Google IO 2019
Android CameraX : Abstracting Camera2 API
Camera2 API provided us a lot of fine grain control over the sensors. More so, it required a lot of boilerplate code. It required communication with the HAL (Hardware Acceleration Layer) in order to access the hardware and drivers of the camera.
What CameraX essentially does is abstract all of this.
Hence CameraX provides ease of use, thanks to more readability and less boilerplate code.
Android CameraX : Use Cases
CameraX has come up with a use case-based approach to focus on the task you need to get done instead of spending time managing device-specific configurations.
The three core use cases are:
- Preview – What you see. The Camera Feed.
- Image Analysis – What you do. Processing the Camera Feed.
- Image Capture – What you keep. Capturing the photo/video.
Android CameraX Extensions
Extensions basically allow us to use device-native camera features directly in our custom camera application with just a few lines of code.
Features like portrait mode, depth, Bokeh effect if supported by the device can be integrated into the use cases easily.
onResume()
and onPause()
, we can do that, using CameraX.bindToLifecycle()
.Enough talk. Let’s deep dive into CameraX code now.
In the section below, we’ll be implementing two use cases – Preview and Image Capture. Image Analyzer would be covered in a separate tutorial.
CameraX Example Project Structure

Android Camerax Project Structure
CameraX Implementation Code
Let’s set up the build.gradle
first:
implementation 'androidx.appcompat:appcompat:1.1.0-alpha03'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
def cameraxVersion = "1.0.0-alpha02"
implementation "androidx.camera:camera-core:${cameraxVersion}"
implementation "androidx.camera:camera-camera2:${cameraxVersion}"
We need to add the permissions for Camera and External Storage in the AndroidManifest.xml file.
The code for the activity_main.xml layout is given below:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:app="https://schemas.android.com/apk/res-auto"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextureView
android:id="@+id/view_finder"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<ImageButton
android:id="@+id/imgCapture"
android:layout_width="72dp"
android:layout_height="72dp"
android:layout_margin="24dp"
app:srcCompat="@android:drawable/ic_menu_camera"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
The code for the MainActivity.java
class is given below.
package com.journaldev.androidcamerax;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.camera.core.CameraX;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCaptureConfig;
import androidx.camera.core.Preview;
import androidx.camera.core.PreviewConfig;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.LifecycleOwner;
import android.content.pm.PackageManager;
import android.graphics.Matrix;
import android.os.Bundle;
import android.os.Environment;
import android.util.Rational;
import android.util.Size;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import java.io.File;
public class MainActivity extends AppCompatActivity {
private int REQUEST_CODE_PERMISSIONS = 101;
private final String[] REQUIRED_PERMISSIONS = new String[]{"android.permission.CAMERA", "android.permission.WRITE_EXTERNAL_STORAGE"};
TextureView textureView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textureView = findViewById(R.id.view_finder);
if(allPermissionsGranted()){
startCamera(); //start camera if permission has been granted by user
} else{
ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
}
}
private void startCamera() {
CameraX.unbindAll();
Rational aspectRatio = new Rational (textureView.getWidth(), textureView.getHeight());
Size screen = new Size(textureView.getWidth(), textureView.getHeight()); //size of the screen
PreviewConfig pConfig = new PreviewConfig.Builder().setTargetAspectRatio(aspectRatio).setTargetResolution(screen).build();
Preview preview = new Preview(pConfig);
preview.setOnPreviewOutputUpdateListener(
new Preview.OnPreviewOutputUpdateListener() {
//to update the surface texture we have to destroy it first then re-add it
@Override
public void onUpdated(Preview.PreviewOutput output){
ViewGroup parent = (ViewGroup) textureView.getParent();
parent.removeView(textureView);
parent.addView(textureView, 0);
textureView.setSurfaceTexture(output.getSurfaceTexture());
updateTransform();
}
});
ImageCaptureConfig imageCaptureConfig = new ImageCaptureConfig.Builder().setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY)
.setTargetRotation(getWindowManager().getDefaultDisplay().getRotation()).build();
final ImageCapture imgCap = new ImageCapture(imageCaptureConfig);
findViewById(R.id.imgCapture).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
File file = new File(Environment.getExternalStorageDirectory() + "/" + System.currentTimeMillis() + ".png");
imgCap.takePicture(file, new ImageCapture.OnImageSavedListener() {
@Override
public void onImageSaved(@NonNull File file) {
String msg = "Pic captured at " + file.getAbsolutePath();
Toast.makeText(getBaseContext(), msg,Toast.LENGTH_LONG).show();
}
@Override
public void onError(@NonNull ImageCapture.UseCaseError useCaseError, @NonNull String message, @Nullable Throwable cause) {
String msg = "Pic capture failed : " + message;
Toast.makeText(getBaseContext(), msg,Toast.LENGTH_LONG).show();
if(cause != null){
cause.printStackTrace();
}
}
});
}
});
//bind to lifecycle:
CameraX.bindToLifecycle((LifecycleOwner)this, preview, imgCap);
}
private void updateTransform(){
Matrix mx = new Matrix();
float w = textureView.getMeasuredWidth();
float h = textureView.getMeasuredHeight();
float cX = w / 2f;
float cY = h / 2f;
int rotationDgr;
int rotation = (int)textureView.getRotation();
switch(rotation){
case Surface.ROTATION_0:
rotationDgr = 0;
break;
case Surface.ROTATION_90:
rotationDgr = 90;
break;
case Surface.ROTATION_180:
rotationDgr = 180;
break;
case Surface.ROTATION_270:
rotationDgr = 270;
break;
default:
return;
}
mx.postRotate((float)rotationDgr, cX, cY);
textureView.setTransform(mx);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if(requestCode == REQUEST_CODE_PERMISSIONS){
if(allPermissionsGranted()){
startCamera();
} else{
Toast.makeText(this, "Permissions not granted by the user.", Toast.LENGTH_SHORT).show();
finish();
}
}
}
private boolean allPermissionsGranted(){
for(String permission : REQUIRED_PERMISSIONS){
if(ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED){
return false;
}
}
return true;
}
}
PreviewConfig
is where we configure the preview which is the live camera feed.
In the builder, we can set stuff like Aspect Ratios, Lens front or back, and target resolution.
The Preview is displayed on a TextureView
.
ImageCaptureConfiguration.Builder()
is configuration for image when captured. We set different configuration like MIN_LATENCY or MAX_QUALITY.
An output screenshot of the application in action is given below:

Android Camerax Output
That brings an end to this tutorial. You can download the project from the link below or view the full source code from the Github Link provided underneath.
How can i set different size to preview dynamically
you have commented a line for front camera but when i uncomment that line app start force close
ImageCaptureConfig imageCaptureConfig = new ImageCaptureConfig.Builder().setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY)
.setTargetRotation(getWindowManager()
.getDefaultDisplay()
.getRotation())
.setLensFacing(CameraX.LensFacing.FRONT) //add this part in
.build();
Thanks to this solution : https://stackoverflow.com/questions/56159804/java-lang-illegalargumentexception-suggested-resolution-map-missing-resolution
this code is not working on One Plus 3T , if you guys have any solution please help
Its working great man!! Awesome
Code not working on android 10(Q) version. Error throwing pic captured failed:Failed to write or close the file
You probably haven’t set read and write to external storage permissions in your manifest file. Your app must have Camera, Read& Write to external storage or it will crash or throw some error as in this case.
In Android 10 and above,
add android:requestLegacyExternalStorage=”true” in the application tag in AndroidManifest.xml
Is anyone else having an issue where the TextureView isn’t rotating properly on device reorientation?
you could do this or you could just use an intent so the built-in camera app does the work for you.
Custom Cameras allow you lots of customizations. Face Detection and other video processing stuff. You can even customise ISO, Shutter and Exposure of the camera.
Please, how can we customize the ISO, Shutter and Exposure? I’ve searching and I have come up with very little.
CameraX has recently moved to the beta phase after being in the alpha phase for a while. Lots of features yet to be implemented.
Hi
Good one,
but the Preview camera window ‘s width is stretched. tried different ways to set width and height of texture view and aspect ratio but wasn’t much helpfull . I could avoid stretching but preview quality got decreased
suggest if have any solution
Thanks
Hello! Thanks for the tutorial 🙂
I followed all the steps of the tutorial. The camera opens normally, but the app is not saving the pic. Even if all the permissions granted, it is not allowing to save the pic. This is the error:
2019-07-05 00:45:11.710 28373-28373/com.example.pegadamonique W/System.err: java.io.FileNotFoundException: /storage/emulated/0-1562298311316.png (Permission denied)
2019-07-05 00:45:11.711 28373-28373/com.example.pegadamonique W/System.err: at java.io.FileOutputStream.open0(Native Method)
2019-07-05 00:45:11.711 28373-28373/com.example.pegadamonique W/System.err: at java.io.FileOutputStream.open(FileOutputStream.java:308)
2019-07-05 00:45:11.711 28373-28373/com.example.pegadamonique W/System.err: at java.io.FileOutputStream.(FileOutputStream.java:238)
2019-07-05 00:45:11.711 28373-28373/com.example.pegadamonique W/System.err: at java.io.FileOutputStream.(FileOutputStream.java:180)
2019-07-05 00:45:11.711 28373-28373/com.example.pegadamonique W/System.err: at androidx.camera.core.ImageSaver.run(ImageSaver.java:75)
2019-07-05 00:45:11.711 28373-28373/com.example.pegadamonique W/System.err: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
2019-07-05 00:45:11.711 28373-28373/com.example.pegadamonique W/System.err: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
2019-07-05 00:45:11.711 28373-28373/com.example.pegadamonique W/System.err: at java.lang.Thread.run(Thread.java:764)
He forgot to add the permissions on the AndroidManifest, this 2 lines and you will be ok