Android: setTorchMode promise is fire-and-forget; 'off' on a flash-less camera (front camera) rejects with 'No flash unit' as an unhandled rejection on every mount

Author: nicksrandallCreated Jul 7, 2026Updated Aug 3, 2026

What's happening?

Setting the torchMode prop to any value — including 'off' — on a camera whose bound camera has no flash unit produces an unhandled promise rejection on every mount/update:

Error: java.lang.IllegalStateException: No flash unit
  at androidx.camera.camera2.impl.TorchControl.setTorchAsync-Oup_wC0$camera_camera2(TorchControl.kt:128)
  at androidx.camera.camera2.adapter.CameraControlAdapter.enableTorch(CameraControlAdapter.kt:115)
  at androidx.camera.core.impl.AdapterCameraControl.enableTorch(AdapterCameraControl.java:73)
  at com.margelo.nitro.camera.hybrids.HybridCameraController$setTorchMode$1.invokeSuspend(HybridCameraController.kt:205)
  ...
  mechanism: onunhandledrejection

Two compounding causes in [email protected]:

  1. useTorchModeUpdater never handles rejection (src/hooks/internal/useTorchModeUpdater.ts):
typescript
useEffect(() => {
  if (controller == null) return
  if (torchMode == null) return
  controller.setTorchMode(torchMode)   // ← returned Promise is neither awaited nor .catch()-ed
}, [controller, torchMode])

Any native failure becomes a global unhandled rejection the app cannot intercept (there is no onError route for it, and no imperative handle to try/catch).

  1. setTorchMode calls enableTorch unconditionally (android/.../HybridCameraController.kt:200): CameraX's TorchControl fails the returned future with IllegalStateException("No flash unit") for any enableTorch(...) call on a camera without a flash unit — even enableTorch(false). There is no hasFlashUnit pre-check.

The failure mode is easy to hit accidentally: an app that renders torchMode={torchOn ? 'on' : 'off'} (a natural way to write a declarative prop) emits one Sentry-reported unhandled rejection per mount of any flash-less camera — the front camera included. In our production app (Sentry ID ALLY-HOME-NATIVE-22) this produced 4,438 events across 190 users in 5 days, almost all from front-camera screens where the torch was never touched.

It can also occur with a correctly-guarded prop: on some logical multi-camera devices (observed on a Galaxy Z Fold) device.hasTorch is true while the bound physical camera has no flash unit, so even torchMode='on' behind a hasTorch check rejects.

Reproduction

  1. <Camera device={frontDevice} isActive torchMode="off" ... /> (or any device whose bound camera lacks a flash unit) on Android.
  2. Observe an unhandled promise rejection java.lang.IllegalStateException: No flash unit on mount — no torch interaction needed.

Suggested fix

Either (ideally both):

  • In useTorchModeUpdater, handle the promise: controller.setTorchMode(torchMode).catch(...) — route to the camera onError handler or log, so library-internal failures can't become app-level unhandled rejections. (Same pattern applies to the other fire-and-forget updaters.)
  • In the Android setTorchMode, no-op (or reject with a typed, documented error) when the camera has no flash unit — camera.cameraInfo.hasFlashUnit() — at minimum for the OFF case, which is always semantically a no-op on a flash-less camera.

Workaround

Pass torchMode={hasTorch ? (torchOn ? 'on' : 'off') : undefined}useTorchModeUpdater skips null/undefined, so the native call never happens. This doesn't cover the hasTorch-mismatch case on logical multi-cameras.

Environment

  • react-native-vision-camera 5.1.0 (Nitro), react-native 0.86.0, Expo SDK 57, New Architecture
  • Android (CameraX path); observed on Samsung SM-A156U (Galaxy A15), Galaxy Z Fold, and other devices

Source: mrousavy/react-native-vision-camera