Camera2 & CameraX (Java)
RecipeBox's recipe editor lets a user attach a photo of their finished dish (stored as the Recipe class's photoUri field). Getting that photo means talking to the device's camera, and Android gives two very different ways to do that.
Camera2: Powerful but Painful
Camera2 is Android's low-level camera API. It exposes almost everything the hardware can do — manual focus, exposure, per-frame capture sessions — but that power comes at a real cost. Camera2 is notoriously inconsistent across device manufacturers: a capture session configured one way behaves correctly on a Pixel and subtly wrong on a budget phone from a different vendor, orientation and rotation handling has to be worked out by hand, and getting a reliable preview-plus-capture pipeline working across the wide spread of real Android hardware historically took a lot of device-specific workaround code. It's honest to say most apps that don't need Camera2's fine-grained control are better off avoiding it directly.
CameraX: Built on Camera2, Smoothing It Over
CameraX is a Jetpack library built on top of Camera2 that deliberately absorbs those device inconsistencies so RecipeBox's code doesn't have to. It exposes a small set of use cases — Preview, ImageCapture, ImageAnalysis — and handles the device-specific quirks internally. For a straightforward "take a photo of your dish" screen, CameraX is the right choice.
Adding the Dependency
// app/build.gradle
dependencies {
implementation 'androidx.camera:camera-core:1.3.4'
implementation 'androidx.camera:camera-camera2:1.3.4'
implementation 'androidx.camera:camera-lifecycle:1.3.4'
implementation 'androidx.camera:camera-view:1.3.4'
}
PreviewView in the Layout
<!-- activity_capture_dish.xml -->
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Button
android:id="@+id/btnCapture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:text="Capture Dish Photo" />
The CAMERA Runtime Permission
Like location in the earlier lesson, camera access is a runtime-dangerous permission covered by the Runtime Permissions lesson. RecipeBox must request and receive Manifest.permission.CAMERA before binding any CameraX use case — attempting to bind without it throws a SecurityException.
Binding Preview & ImageCapture to the Lifecycle
// CaptureDishActivity.java (CAMERA permission already granted)
private ImageCapture imageCapture;
private void startCamera() {
ListenableFuture<ProcessCameraProvider> cameraProviderFuture =
ProcessCameraProvider.getInstance(this);
cameraProviderFuture.addListener(() -> {
try {
ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
Preview preview = new Preview.Builder().build();
PreviewView previewView = findViewById(R.id.previewView);
preview.setSurfaceProvider(previewView.getSurfaceProvider());
imageCapture = new ImageCapture.Builder().build();
CameraSelector cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA;
cameraProvider.unbindAll();
cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageCapture);
} catch (ExecutionException | InterruptedException e) {
Log.e("RecipeBoxCamera", "Camera init failed", e);
}
}, ContextCompat.getMainExecutor(this));
}
Capturing a Photo & Saving via FileProvider
The Content Providers lesson covers FileProvider in detail; here it's used to get a safe, shareable URI for the captured photo file rather than exposing a raw file path.
private void captureDishPhoto() {
File photoFile = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES),
"recipe_dish_" + System.currentTimeMillis() + ".jpg");
ImageCapture.OutputFileOptions outputOptions =
new ImageCapture.OutputFileOptions.Builder(photoFile).build();
imageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this),
new ImageCapture.OnImageSavedCallback() {
@Override
public void onImageSaved(ImageCapture.OutputFileResults output) {
Uri photoUri = FileProvider.getUriForFile(
CaptureDishActivity.this,
"com.codingwithsonu.recipebox.fileprovider",
photoFile);
saveUriToRecipe(photoUri); // sets Recipe.photoUri
}
@Override
public void onError(ImageCaptureException exception) {
Log.e("RecipeBoxCamera", "Capture failed", exception);
}
});
}
Rotation Handled Automatically
One more genuinely painful part of raw Camera2 used to be figuring out sensor orientation versus device rotation by hand — get it wrong and photos come out sideways depending on how the phone was held. CameraX detects the device's rotation and the camera sensor's orientation together and corrects the saved image automatically, so a dish photo taken in portrait or landscape on any supported device comes out right-side up without RecipeBox writing any rotation-matrix code itself.
CAMERA permission
granted at runtime
bindToLifecycle()
Preview + ImageCapture
takePicture()
saves JPEG file
FileProvider
content:// URI → Recipe.photoUri