DEV Community

Cover image for My Camera App Was Crashing When Users Mixed HDR + 60 FPS — CameraX Fixed It
SuriDevs
SuriDevs

Posted on • Originally published at suridevs.com

My Camera App Was Crashing When Users Mixed HDR + 60 FPS — CameraX Fixed It

A user filed a crash report. Their phone supported HDR. It supported 60 FPS. I'd tested both features separately and they worked fine. But the moment they enabled both together, the camera session died with an IllegalStateException buried somewhere deep in the camera HAL.

I couldn't reproduce it — my Pixel handled the combo just fine. Their mid-range device did not.

That's the kind of bug that follows you. You add a device to the "doesn't support X+Y" list in your code. Another device shows up a month later. The list grows. Nobody is happy.

CameraX's Feature Groups API is the answer to this. You query whether a combination of features works before you try to enable it. No list-keeping. No trial and error. No crashes.


The Old Way Was Just Guessing

Previously you'd enable features one at a time and hope for the best:

// Old approach - optimism as a strategy
videoCapture.apply {
    setHdr(true)
    setTargetFrameRate(Range(60, 60))
    setStabilization(true)
}
// Might work. Might crash. Might silently fail. Fun times.
Enter fullscreen mode Exit fullscreen mode

Some devices support HDR. Some support 60 FPS. Some support HDR + 60 FPS together. Others have the hardware for both individually but the camera HAL can't run them simultaneously. The only way to know was to test — or let users find out the hard way.


How Feature Groups Actually Work

You ask the device: can you do these things at the same time?

val features = setOf(
    GroupableFeature.HDR_HLG10,
    GroupableFeature.FPS_60,
    GroupableFeature.PREVIEW_STABILIZATION
)

if (cameraInfo.isFeatureGroupSupported(features)) {
    // All three work together — safe to proceed
} else {
    // Need to fall back to something the device can handle
}
Enter fullscreen mode Exit fullscreen mode

Or you can hand CameraX a wish list and let it find the best supported subset automatically:

cameraProvider.bindToLifecycle(
    lifecycleOwner,
    cameraSelector,
    preview,
    videoCapture,
    SessionConfig.defaultSessionConfig(
        preferredFeatureGroup = listOf(
            GroupableFeature.HDR_HLG10,
            GroupableFeature.FPS_60,
            GroupableFeature.PREVIEW_STABILIZATION
        )
    )
)
Enter fullscreen mode Exit fullscreen mode

With preferredFeatureGroup, CameraX tries your full list first, then drops features until it finds a combination the device supports. On a Pixel 10 Pro you get all three. On an older budget phone you might get just stabilization. Either way, no crash.


What Features Are Available

The current GroupableFeature set:

  • HDR_HLG10 — HDR video in HLG10 format
  • FPS_60 — 60 FPS recording
  • PREVIEW_STABILIZATION — Real-time stabilization during preview
  • IMAGE_ULTRA_HDR — Ultra HDR still image capture

More are expected as the API matures.


Building a UI That Doesn't Lie

The real win is building camera UIs that reflect what the device actually supports. Check each feature, then check combinations:

fun updateFeatureToggles(cameraInfo: CameraInfo) {
    hdrToggle.isEnabled = cameraInfo.isFeatureGroupSupported(
        setOf(GroupableFeature.HDR_HLG10)
    )
    fps60Toggle.isEnabled = cameraInfo.isFeatureGroupSupported(
        setOf(GroupableFeature.FPS_60)
    )
    stabilizationToggle.isEnabled = cameraInfo.isFeatureGroupSupported(
        setOf(GroupableFeature.PREVIEW_STABILIZATION)
    )
}

fun onFeatureToggled() {
    val requested = mutableSetOf<GroupableFeature>()
    if (hdrToggle.isChecked) requested.add(GroupableFeature.HDR_HLG10)
    if (fps60Toggle.isChecked) requested.add(GroupableFeature.FPS_60)
    if (stabilizationToggle.isChecked) requested.add(GroupableFeature.PREVIEW_STABILIZATION)

    if (cameraInfo.isFeatureGroupSupported(requested)) {
        recordButton.isEnabled = true
        warningText.visibility = View.GONE
    } else {
        recordButton.isEnabled = false
        warningText.text = "This combination isn't supported on your device"
        warningText.visibility = View.VISIBLE
    }
}
Enter fullscreen mode Exit fullscreen mode

Users see exactly what their hardware can do. They can't select broken combos. Nobody gets to the crash.


required vs preferred — Pick the Right One

requiredFeatureGroup — all features must be supported, or bindToLifecycle() throws an IllegalArgumentException. Use it only when you genuinely can't ship without the specific combo (a pro video app where "HDR + 60 FPS" is the product).

preferredFeatureGroup — CameraX picks the best supported subset and never throws. Use this for almost everything.

One important gotcha with preferredFeatureGroup: it silently drops features it can't support. That's intentional, but make sure your UI reflects what actually ran — inspect the actual SessionConfig after binding, not the list you passed in. If the user toggled HDR + 60 FPS and they got HDR only, your record button shouldn't say otherwise.


What to Watch Out For in Production

Don't query before you have a CameraProvider. isFeatureGroupSupported() only works after cameraProvider.bindToLifecycle() has resolved. Call it in your ViewModel init block and you'll get a crash or stale data.

Configuration changes invalidate everything. Screen rotation, navigation pop-and-return — you have to re-bind the camera and re-check support. Don't cache support results across the UI lifecycle.

Camera ops are async — use coroutines. Wrap ListenableFuture<ProcessCameraProvider> in await(). Don't chain callbacks next to Feature Group logic or you'll have a race.

Front vs back camera. CameraInfo is camera-specific. Run the support check per cameraSelector — don't assume the back camera results apply to the front.


Dependencies

dependencies {
    val cameraxVersion = "1.6.1"
    implementation("androidx.camera:camera-camera2:$cameraxVersion")
    implementation("androidx.camera:camera-lifecycle:$cameraxVersion")
    implementation("androidx.camera:camera-video:$cameraxVersion")
}
Enter fullscreen mode Exit fullscreen mode

Feature Groups graduated from experimental to stable in CameraX 1.5. The current stable is 1.6.1. Check the AndroidX CameraX release notes before pinning a version — the 1.6.x line has been adding groupable features steadily.


The Payoff

Before Feature Groups, a camera app that worked well across devices needed a combination of: manual device testing, a hard-coded compat list, or just disabling advanced features entirely and leaving flagship users with the same experience as budget users.

Now you query capabilities at runtime and build UI that adapts. Flagship gets flagship features. Budget gets what it supports. Nobody gets a crash report.

If you're touching camera code, this API earns its upgrade cost.


Originally published at suridevs.com

Top comments (0)