Introduction to Android’s CameraX With Java

M. Van Luke
The Startup
Published in
7 min readAug 7, 2020

Welcome! In this tutorial, we are going to build a fun application that detects your camera’s orientation with CameraX.

We’ll take it step by step and you can pause at any section if you feel like it. However, there are some prerequisites if you want to follow along without a headache.

You can find the source code on my Github repository. Let me know if something is unclear/doesn’t work for you.

Prerequisites

  1. Previous experience with Android Development or Java to understand the syntax and structure.
  2. Access to Android Studio and an emulator or an android device with a camera.
  3. Ability to follow instructions.

Now that we got that out of the way, it’s time to get our hands dirty.

Set Up

  1. Create a New Project in Android Studio and choose the Empty Activity when prompted.
  2. In the Project Configuration window select Java as the language and API 22: Android 5.1 (Lollipop) as the Minimum SDK.

Important: Make sure that you have the latest Android SDK installed on your system.

Project Configuration Settings

Dependencies & Permissions

  1. Find the build.gradle(Module:app) file under the Gradle Scripts directory.

2. Scroll to the bottom of the file and find the dependencies block. Add the following CameraX dependencies.

def camerax_version = "1.0.0-beta07"
implementation "androidx.camera:camera-camera2:$camerax_version"
implementation "androidx.camera:camera-lifecycle:$camerax_version"
implementation "androidx.camera:camera-view:1.0.0-alpha14"
build.gradle(Module:app) dependencies

3. In the same file, find the android block and add the following to the bottom before the closing bracket.

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
Compile Options in build.gradle(Module:app)

4. Now, let’s add the camera permissions. Open the AndroidManifest.xml under the Manifests directory and add the following lines.

<uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.CAMERA" />
Example AndroidManifest.xml with camera permissions

5. Sync Gradle settings by pressing on the toolbar icon.

Sync files with Gradle settings

And that’s it. We’re done with the dependencies and permissions. The next steps involve coding the logic and connecting it to the user interface.

Step 1: Prompt the user for camera permission

If we don’t give camera access to our application, we can’t do anything. Luckily, Android already provides us with the logic we need for this part.

Permission Logic

  • Check if the permission has been granted
  • If yes, enable camera; otherwise, ask for permission
  1. Find the MainActivity.java file under the app/java/{package_name} directory.

In my case, it’s app/java/com.camerax.tutorial

2. Add the following code block in the onCreate method.

Button enableCamera = findViewById(R.id.enableCamera);
enableCamera.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (hasCameraPermission()) {
enableCamera();
} else {
requestPermission();
}
}
});

This adds a listener to the button that we’re going to create in the user interface. When a user clicks it, the permission logic is invoked.

Now, we need to actually implement these methods. All of them are in the same file (MainActivity.java).

Method 1: hasCameraPermission()

Returns a boolean value (True/False) depending on whether the user has given camera permission to our app.

private boolean hasCameraPermission() {
return ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED;
}

Method 2: requestPermission()

Requests permission from the user so that our app can access the device’s camera and perform the image analysis.

private void requestPermission() {
ActivityCompat.requestPermissions(
this,
CAMERA_PERMISSION,
CAMERA_REQUEST_CODE
);
}

Method 3: enableCamera()

Creates a new Intent object to start a CameraActivity.

Don’t worry about the CameraActivity class, we will create it in the next steps.

private void enableCamera() {
Intent intent = new Intent(this, CameraActivity.class);
startActivity(intent);
}

The code so far:

public class MainActivity extends AppCompatActivity {
private static final String[] CAMERA_PERMISSION = new String[]{Manifest.permission.CAMERA};
private static final int CAMERA_REQUEST_CODE = 10;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Button enableCamera = findViewById(R.id.enableCamera);
enableCamera.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (hasCameraPermission()) {
enableCamera();
} else {
requestPermission();
}
}
});
}

private boolean hasCameraPermission() {
return ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED;
}

private void requestPermission() {
ActivityCompat.requestPermissions(
this,
CAMERA_PERMISSION,
CAMERA_REQUEST_CODE
);
}

private void enableCamera() {
Intent intent = new Intent(this, CameraActivity.class);
startActivity(intent);
}

}

Step 2: Create the CameraActivity class

This activity is responsible for three main tasks. Initialize a camera provider, bind the image analysis case to the camera provider so that we can use the camera and analyse images (pretty self-explanatory), and keep track of the device’s camera rotation.

To start fleshing out the code, let’s start with a few simple steps.

  1. Create a new CameraActivity class that extends AppCompatActivity in the same directory as the MainActivity class.
  2. Define the following instance variables.
private PreviewView previewView;
private ListenableFuture<ProcessCameraProvider> cameraProviderFuture;
private TextView textView;

Now, we have to implement the onCreate method which exists by default in the MainActivity class.

Method 1: onCreate()

Initializes the instance variables and binds a camera provider so that we can bind the image analysis case to it.

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_camera);
previewView = findViewById(R.id.previewView);
cameraProviderFuture = ProcessCameraProvider.getInstance(this);
textView = findViewById(R.id.orientation);
cameraProviderFuture.addListener(new Runnable() {
@Override
public void run() {
try {
ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
bindImageAnalysis(cameraProvider);
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}
}, ContextCompat.getMainExecutor(this));

}

Now that we have our onCreate method, we can implement the method that does all the hard work: the bindImageAnalysis method.

Method 2: bindImageAnalysis()

Binds the ImageAnalyzer to the camera provider created in the onCreate method and listens for changes in the camera’s rotation.

private void bindImageAnalysis(@NonNull ProcessCameraProvider cameraProvider) {
ImageAnalysis imageAnalysis =
new ImageAnalysis.Builder().setTargetResolution(new Size(1280, 720))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST).build();
imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(this), new ImageAnalysis.Analyzer() {
@Override
public void analyze(@NonNull ImageProxy image) {
image.close();
}
});
OrientationEventListener orientationEventListener = new OrientationEventListener(this) {
@Override
public void onOrientationChanged(int orientation) {
textView.setText(Integer.toString(orientation));
}
};
orientationEventListener.enable();
Preview preview = new Preview.Builder().build();
CameraSelector cameraSelector = new CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK).build();
preview.setSurfaceProvider(previewView.createSurfaceProvider());
cameraProvider.bindToLifecycle((LifecycleOwner)this, cameraSelector,
imageAnalysis, preview);
}

The code so far:

public class CameraActivity extends AppCompatActivity {
private PreviewView previewView;
private ListenableFuture<ProcessCameraProvider> cameraProviderFuture;
private TextView textView;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_camera);
previewView = findViewById(R.id.previewView);
cameraProviderFuture = ProcessCameraProvider.getInstance(this);
textView = findViewById(R.id.orientation);
cameraProviderFuture.addListener(new Runnable() {
@Override
public void run() {
try {
ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
bindImageAnalysis(cameraProvider);
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}
}, ContextCompat.getMainExecutor(this));
}


private void bindImageAnalysis(@NonNull ProcessCameraProvider cameraProvider) {
ImageAnalysis imageAnalysis =
new ImageAnalysis.Builder().setTargetResolution(new Size(1280, 720))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST).build();
imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(this), new ImageAnalysis.Analyzer() {
@Override
public void analyze(@NonNull ImageProxy image) {
image.close();
}
});
OrientationEventListener orientationEventListener = new OrientationEventListener(this) {
@Override
public void onOrientationChanged(int orientation) {
textView.setText(Integer.toString(orientation));
}
};
orientationEventListener.enable();
Preview preview = new Preview.Builder().build();
CameraSelector cameraSelector = new CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK).build();
preview.setSurfaceProvider(previewView.createSurfaceProvider());
cameraProvider.bindToLifecycle((LifecycleOwner)this, cameraSelector,
imageAnalysis, preview);
}
}

We are now done with the logic. It’s time to move on to the user interface and connect it to our logic.

Step 3: Defining the user interface

Before we do any changes in the layout folder, we need to register our new CameraActivity activity in the AndroidManifest.xml we edited in previous steps.

  1. Put the following snippet in the application block of your AndroidManifest.xml.
<activity android:name=".CameraActivity"/>
Registered CameraActivity in AndroidManifest.xml

Awesome! We’re almost there.

Screen 1: activity_main.xml

In our MainActivity class we defined a Button named “enableCamera”. Now, it’s time to create it in the activity_main.xml.

  1. Find the activity_main.xml file under the res/layout directory.
  2. Replace the code with the following snippet.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="500dp"
android:id="@+id/container"
android:textSize="20sp"
android:layout_gravity="center"
android:text="This is a CameraX test app!"
android:gravity="center">
</TextView>

<Button
android:id="@+id/enableCamera"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="Open Camera" />
</LinearLayout>


</androidx.constraintlayout.widget.ConstraintLayout>

The Button component is connected to our logic through the id attribute.

When clicked, it either asks the user for permission or it enables the camera.

But, we have also a CameraActivity class that also needs a user interface.

Screen 1: Main Activity
Ask user permission

Screen 2: activity_camera.xml

We need two components here. The “viewfinder” in which we can see the camera’s live feed and a text view so that we can show the camera’s orientation.

  1. In the same folder as activity_main.xml, create a new file named activity_camera.xml.
  2. Copy the following code.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".CameraActivity">

<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/container">
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/orientation"
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_gravity="center"
android:gravity="center"
android:textSize="100sp"
android:textColor="#9999ff"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

In the FrameLayout, we have a PreviewView that shows the camera’s live feed. The TextView component in the LinearLayout displays the changing orientation of the camera. You can customize the xml files as you wish, but you should keep in mind that if you change the id attribute here, you also have to change it in each class accordingly.

Screen 2: Camera Activity

As you can see in the preview, the counter says 0 because the emulator’s default orientation is 0.

I recommend you test this application on your device so that you see the number change.

And that’s it! We’re done.

I hope you learned something with this tutorial.

If you’ve made it this far, thank you. In case you have any questions or any trouble setting things up, don’t hesitate to contact me. I’ll be glad to help you out.

🌹

--

--

M. Van Luke
The Startup

Software engineer slightly obsessed with data and snakes. Find me on IG @envenomation