A high-performance React Native library for controlling the device's flashlight/torch, powered by NitroModules.
demo.mov
Try it yourself: Check out the full example app in the
example/folder.
- Zero Bridge Overhead - Direct native module access via NitroModules for maximum performance
- Full Type Safety - TypeScript definitions generated from native specs
- React Hook API - Simple and intuitive
useTorch()hook - Brightness Control - Adjust flashlight intensity on iOS (10 levels) and Android 13+ (device-dependent)
- Real-time State Updates - Native callbacks when torch state or brightness level changes
- Cross-Platform - Works seamlessly on iOS and Android
- Error Handling - Comprehensive error types for different failure scenarios
- React Native >= 0.76
- iOS: Deployment Target >= 13.0
- Android:
minSdk>= 23 (Android 6.0 Marshmallow) for basic torch functionalitycompileSdk>= 34 (required byreact-native-nitro-modules)- For brightness control: API 33+ (Android 13)
react-native-nitro-modules>= 0.3x.x
Using npm:
npm install react-native-torch-nitro react-native-nitro-modulesUsing yarn:
yarn add react-native-torch-nitro react-native-nitro-modulesUsing pnpm:
pnpm add react-native-torch-nitro react-native-nitro-modules
react-native-nitro-modulesis required as this library relies on Nitro Modules.
cd ios && pod installNo additional configuration required. Camera/flashlight access doesn't require special permissions on iOS for torch usage.
No additional permissions required! Since Android 6.0 (API 23), the CameraManager.setTorchMode() API doesn't require CAMERA permission.
Optionally, you can add a feature declaration to your AndroidManifest.xml to indicate flash support:
<uses-feature android:name="android.hardware.camera.flash" android:required="false" />The
android:required="false"attribute allows your app to run on devices without a flash, but you should handleNoFlashAvailableerrors appropriately.
import React, { useState } from 'react'
import { View, Button, Text, Slider } from 'react-native'
import { useTorch } from 'react-native-torch-nitro'
export default function App() {
const [currentLevel, setCurrentLevel] = useState<number | null>(null)
const { on, off, setLevel, getMaxLevel } = useTorch({
onStateChanged: (state) => {
console.log('Torch is now:', state ? 'ON' : 'OFF')
},
onLevelChanged: (level) => {
console.log('Brightness level changed to:', level)
setCurrentLevel(level)
},
onError: (error) => {
console.error('Torch error:', error.code, error.message)
},
})
const maxLevel = getMaxLevel() // null if brightness control not supported
return (
<View style={{ flex: 1, justifyContent: 'center', padding: 20 }}>
<Text style={{ fontSize: 18, marginBottom: 20, textAlign: 'center' }}>
Torch is {currentLevel !== null ? 'ON 🔦' : 'OFF'}
</Text>
<Button title="Turn On" onPress={on} />
<Button title="Turn Off" onPress={off} />
{maxLevel && maxLevel > 1 && (
<View style={{ marginTop: 20 }}>
<Text>Brightness Level: {currentLevel ?? 'N/A'}</Text>
<Slider
minimumValue={1}
maximumValue={maxLevel}
step={1}
value={currentLevel ?? 1}
onValueChange={(value) => setLevel(value)}
/>
</View>
)}
</View>
)
}React hook for controlling the device flashlight with real-time state updates.
Parameters:
interface UseTorchOptions {
/**
* Called when the torch state changes (true = on, false = off)
*/
onStateChanged?: (isOn: boolean) => void
/**
* Called when the torch brightness level changes
* null when torch is off, number (1 to maxLevel) when on
*/
onLevelChanged?: (level: number | null) => void
/**
* Called when an error occurs (e.g., no flash available, access denied)
*/
onError?: (error: TorchError) => void
}Returns:
{
on: () => Promise<void> // Turn the torch on
off: () => Promise<void> // Turn the torch off
toggle: () => Promise<void> // Toggle the torch on/off
setLevel: (level: number) => Promise<void> // Set brightness level (1 to maxLevel)
getMaxLevel: (dynamic?: boolean) => number | null // Get max brightness level (null if not supported)
// dynamic: iOS only - if true, returns current available level (may be lower under thermal stress)
// - if false/undefined, returns hardware maximum (10)
// Android - parameter is ignored, always returns hardware maximum
}Example:
const { on, off, toggle, setLevel, getMaxLevel } = useTorch({
onStateChanged: (state) => {
console.log('Torch state changed to:', state)
},
onLevelChanged: (level) => {
console.log('Brightness level:', level)
},
onError: (error) => {
if (error.code === 'NoFlashAvailable') {
Alert.alert('Error', 'Your device does not have a flashlight')
}
},
})
// Simple toggle
await toggle()
// Or use on/off explicitly
await on()
await off()
// Check if brightness control is supported
const maxLevel = getMaxLevel()
if (maxLevel && maxLevel > 1) {
// Device supports brightness control
await setLevel(maxLevel / 2) // Set to 50% brightness
}
// iOS: Check current available level (may be lower under thermal stress)
const currentMaxLevel = getMaxLevel(true) // iOS only - returns current available level
const hardwareMaxLevel = getMaxLevel() // or getMaxLevel(false) - returns hardware max (10 on iOS)The library provides specific error codes for different failure scenarios:
enum TorchExceptionType {
/**
* Camera service is not available (Android specific)
*/
CameraServiceUnavailable = 'CameraServiceUnavailable',
/**
* Android API level is too low
* - For basic torch: < Android 6.0 (API 23)
* - For brightness control: < Android 13 (API 33)
*/
ApiLevelTooLow = 'ApiLevelTooLow',
/**
* Device does not have a flash/torch
*/
NoFlashAvailable = 'NoFlashAvailable',
/**
* Device does not support brightness control
* (FLASH_INFO_STRENGTH_MAXIMUM_LEVEL <= 1)
*/
BrightnessControlNotSupported = 'BrightnessControlNotSupported',
/**
* Failed to access or control the torch
*/
AccessFailed = 'AccessFailed',
}Error Object:
interface TorchError {
message: string // Human-readable error message
code: TorchExceptionType // Error type code
}function TorchToggle() {
const [isOn, setIsOn] = useState(false)
const { toggle } = useTorch({
onStateChanged: setIsOn,
})
return (
<Button
title={isOn ? 'Turn Off Flashlight' : 'Turn On Flashlight'}
onPress={toggle}
/>
)
}function TorchManualControl() {
const [isOn, setIsOn] = useState(false)
const { on, off } = useTorch({
onStateChanged: setIsOn,
})
return (
<View>
<Button title="Turn On" onPress={on} disabled={isOn} />
<Button title="Turn Off" onPress={off} disabled={!isOn} />
</View>
)
}function TorchBrightnessControl() {
const [level, setLevel] = useState<number | null>(null)
const { setLevel: setTorchLevel, getMaxLevel } = useTorch({
onLevelChanged: setLevel,
onError: (error) => {
if (error.code === 'BrightnessControlNotSupported') {
Alert.alert(
'Not Supported',
'Your device does not support brightness control'
)
}
},
})
const maxLevel = getMaxLevel()
if (!maxLevel || maxLevel <= 1) {
return <Text>Brightness control not supported on this device</Text>
}
return (
<View>
<Text>
Current Level: {level ?? 'Off'} / {maxLevel}
</Text>
<Slider
minimumValue={1}
maximumValue={maxLevel}
step={1}
value={level ?? 1}
onValueChange={setTorchLevel}
/>
<Button title="Set to Max" onPress={() => setTorchLevel(maxLevel)} />
<Button title="Set to Min" onPress={() => setTorchLevel(1)} />
</View>
)
}function TorchWithErrorHandling() {
const [isOn, setIsOn] = useState(false)
const [errorMessage, setErrorMessage] = useState<string | null>(null)
const { on, off } = useTorch({
onStateChanged: setIsOn,
onError: (error) => {
switch (error.code) {
case 'NoFlashAvailable':
setErrorMessage('Your device does not have a flashlight')
break
case 'CameraServiceUnavailable':
setErrorMessage('Camera service is not available')
break
case 'BrightnessControlNotSupported':
setErrorMessage('Brightness control not supported on this device')
break
case 'AccessFailed':
setErrorMessage('Failed to access flashlight')
break
default:
setErrorMessage(error.message)
}
},
})
return (
<View>
<Button title="Toggle Torch" onPress={isOn ? off : on} />
{errorMessage && <Text style={{ color: 'red' }}>{errorMessage}</Text>}
</View>
)
}function TorchWithTracking() {
const [isOn, setIsOn] = useState(false)
const [history, setHistory] = useState<string[]>([])
const { on, off } = useTorch({
onStateChanged: (state) => {
setIsOn(state)
const timestamp = new Date().toLocaleTimeString()
const entry = `${timestamp}: Torch ${state ? 'ON' : 'OFF'}`
setHistory((prev) => [...prev, entry])
},
onLevelChanged: (level) => {
if (level !== null) {
const timestamp = new Date().toLocaleTimeString()
const entry = `${timestamp}: Level set to ${level}`
setHistory((prev) => [...prev, entry])
}
},
})
return (
<View>
<Button title="Toggle" onPress={isOn ? off : on} />
<Text>Current State: {isOn ? 'ON' : 'OFF'}</Text>
<Text>History:</Text>
{history.map((entry, index) => (
<Text key={index}>{entry}</Text>
))}
</View>
)
}function TorchWithTimer() {
const [isOn, setIsOn] = useState(false)
const { on, off } = useTorch({
onStateChanged: setIsOn,
})
const turnOnWithTimer = async (seconds: number) => {
await on()
setTimeout(() => {
off()
}, seconds * 1000)
}
return (
<View>
<Button
title="Turn on for 5 seconds"
onPress={() => turnOnWithTimer(5)}
/>
<Button title="Turn off now" onPress={off} disabled={!isOn} />
</View>
)
}- Uses
AVCaptureDeviceAPI for torch control - No special permissions required for flashlight usage
- Supports all devices with a flash/torch
- State changes are tracked automatically via native callbacks
- Brightness control: Supports 10 discrete brightness levels (1-10) mapped to AVCaptureDevice's 0.1-1.0 range
- Automatically respects thermal limitations using
maxAvailableTorchLevel(may reduce brightness under thermal stress)
- Uses
CameraManagerAPI (API 23+) - No permissions required -
setTorchMode()doesn't require CAMERA permission since API 23 - Minimum API level: 23 (Android 6.0 Marshmallow)
- Brightness control: Requires API 33+ (Android 13)
- Automatically detects if device supports brightness control via
FLASH_INFO_STRENGTH_MAXIMUM_LEVEL - Returns
nullfromgetMaxLevel()if not supported - Throws
BrightnessControlNotSupportederror when trying to set level on unsupported devices
- Automatically detects if device supports brightness control via
- Native
TorchCallbackprovides real-time updates for state and brightness changes - Automatically handles camera availability and flash unit detection
This usually means the camera service is in use by another app or the system is under heavy load. Try:
- Close other apps that might be using the camera
- Restart the device
For basic torch control:
- Your device is running Android below 6.0 (API 23)
- This library requires Android 6.0 or higher
For brightness control:
- Your device is running Android below 13 (API 33)
- Brightness control requires Android 13 or higher
- Use
getMaxLevel()to check if brightness control is available before callingsetLevel()
Your device's camera flash unit does not support variable brightness levels. This happens when FLASH_INFO_STRENGTH_MAXIMUM_LEVEL <= 1.
Solution:
const maxLevel = getMaxLevel()
if (maxLevel && maxLevel > 1) {
// Brightness control is supported
await setLevel(level)
} else {
// Fall back to basic on/off
await on()
}Your device doesn't have a flashlight/flash unit. This is common on:
- Tablets without cameras
- Some budget smartphones
- Emulators/simulators
The iOS Simulator does not have a flashlight. Test on a physical device.
Thanks to NitroModules, all operations bypass the traditional React Native bridge:
- Zero serialization overhead - Direct C++/Swift/Kotlin communication
- Synchronous native callbacks - Real-time state and brightness level updates via native event system
- Type-safe native calls - No runtime type checking needed
- Automatic state tracking - Android uses
TorchCallbackfor system-level event monitoring - Minimal overhead - Brightness changes are detected at the native level without polling
The brightness control API allows you to adjust the flashlight intensity on supported devices:
-
Check support: Call
getMaxLevel()to get the maximum brightness level- Returns
nullif not supported (Android < 13, or device limitation) - Returns
10on iOS (all devices with torch support) - Returns a number > 1 on Android 13+ if supported (typically 1-5 or higher depending on device)
- Returns
-
Set level: Call
setLevel(level)with a value from 1 tomaxLevel- Level 1 is the minimum brightness
- Level
maxLevelis the maximum brightness - Automatically turns on the torch if it's off
-
Monitor changes: Use
onLevelChangedcallback to track brightness changes- Receives
nullwhen torch is off - Receives the current level (1 to
maxLevel) when torch is on
- Receives
On iOS, the library uses:
AVCaptureDevice.setTorchModeOn(level:)which accepts values from 0.0 to 1.0- The library maps 10 discrete levels (1-10) to the continuous 0.1-1.0 range
- Level 1 = 0.1 (10% brightness), Level 10 = 1.0 (100% brightness)
- Uses
AVCaptureDevice.maxAvailableTorchLevelto respect thermal limitations (may be < 1.0 under thermal duress) - State and level changes are tracked manually and callbacks are fired synchronously
Dynamic Level Detection (iOS only):
getMaxLevel()orgetMaxLevel(false)- returns hardware maximum (always 10)getMaxLevel(true)- returns current available level based on thermal state- Under normal conditions: returns 10 (when
maxAvailableTorchLevel>= 1.0) - Under thermal stress: may return lower values (e.g., 8, 5, 3) as iOS throttles the torch
- This allows you to detect and respond to thermal limitations in real-time
- Note: iOS returns
Float.greatestFiniteMagnitudewhen there are no thermal limitations, which is automatically clamped to 10
- Under normal conditions: returns 10 (when
Important behavior:
- When the torch is turned ON with a specific level,
onLevelChangedreceives the brightness level (1 to 10) - When the torch is turned OFF,
onLevelChangedreceivesnull - Both
onStateChangedandonLevelChangedcallbacks are fired on every state/level change - Under thermal stress, the system may limit the maximum brightness below the requested level
On Android 13+, the library uses:
CameraCharacteristics.FLASH_INFO_STRENGTH_MAXIMUM_LEVELto detect supportCameraManager.turnOnTorchWithStrengthLevel()to set brightnessCameraManager.TorchCallback.onTorchStrengthLevelChanged()for real-time updates
Important behavior:
- When the torch is turned ON with a specific level,
onLevelChangedreceives the brightness level (1 to maxLevel) - When the torch is turned OFF, the native Android callback
onTorchStrengthLevelChanged()is not triggered (even though the strength level internally resets toFLASH_INFO_STRENGTH_DEFAULT_LEVEL) - The library handles this by sending
nulltoonLevelChangedwhenonTorchModeChangeddetects the torch is off - This ensures you always receive a callback when the torch state changes, with
nullindicating the torch is off
function ProgressiveBrightness() {
const { setLevel, getMaxLevel } = useTorch()
const maxLevel = getMaxLevel()
const fadeIn = async () => {
if (!maxLevel || maxLevel <= 1) return
for (let level = 1; level <= maxLevel; level++) {
await setLevel(level)
await new Promise((resolve) => setTimeout(resolve, 200))
}
}
return <Button title="Fade In" onPress={fadeIn} />
}function ThermalAwareTorch() {
const [thermalLevel, setThermalLevel] = useState<number | null>(null)
const { setLevel, getMaxLevel } = useTorch()
// Check current thermal state
const checkThermalState = () => {
const currentMax = getMaxLevel(true) // iOS: get current available level
const hardwareMax = getMaxLevel() // Hardware maximum (10)
setThermalLevel(currentMax)
if (currentMax && hardwareMax && currentMax < hardwareMax) {
Alert.alert(
'Thermal Limitation',
`Device is hot. Max brightness limited to ${currentMax}/${hardwareMax}`
)
}
}
const setMaxBrightness = async () => {
// Use dynamic level to respect thermal limitations
const safeMaxLevel = getMaxLevel(true) ?? getMaxLevel() ?? 1
await setLevel(safeMaxLevel)
}
return (
<View>
<Button title="Check Thermal State" onPress={checkThermalState} />
<Button title="Set Max (Safe)" onPress={setMaxBrightness} />
{thermalLevel && <Text>Current max level: {thermalLevel}</Text>}
</View>
)
}| Feature | iOS | Android | Notes |
|---|---|---|---|
| Basic On/Off | ✅ | ✅ | Works on all supported versions |
| Toggle | ✅ | ✅ | Toggle between on/off states |
| State Callbacks | ✅ | ✅ | Real-time native callbacks |
| Brightness Control | ✅ | ✅ | iOS: 10 levels, Android 13+: device dependent |
| Level Callbacks | ✅ | ✅ | iOS: all versions, Android 13+ only |
| Auto Detection | ✅ | ✅ | Automatically finds camera with flash |
| Permissions Required | ❌ | ❌ | No permissions required on either platform |
Before using brightness control, check if it's supported:
const { getMaxLevel, setLevel, on } = useTorch()
const maxLevel = getMaxLevel()
// Safe approach
const setBrightness = async (level: number) => {
if (maxLevel && maxLevel > 1) {
await setLevel(level)
} else {
// Fall back to basic on/off
console.warn('Brightness control not supported')
await on()
}
}Always provide fallbacks for error scenarios:
const { on, off } = useTorch({
onError: (error) => {
switch (error.code) {
case 'NoFlashAvailable':
// Show UI message that device has no flash
showMessage('Your device does not have a flashlight')
break
case 'BrightnessControlNotSupported':
// Fall back to basic on/off mode
setUseBrightnessControl(false)
break
case 'CameraServiceUnavailable':
// Retry or show message to close other camera apps
showMessage('Please close other camera apps and try again')
break
default:
showMessage(`Error: ${error.message}`)
}
},
})The hook automatically cleans up callbacks, but ensure torch is turned off when appropriate:
useEffect(() => {
return () => {
// Turn off torch when component unmounts
off()
}
}, [off])Prefer callbacks over polling for better performance:
// ✅ Good - uses native callbacks
const [isOn, setIsOn] = useState(false)
const { on, off } = useTorch({
onStateChanged: setIsOn,
})
// ❌ Bad - polling/checking manually is not possible and unnecessaryAlways validate user input before setting brightness:
const handleBrightnessChange = async (value: number) => {
const maxLevel = getMaxLevel()
if (!maxLevel || maxLevel <= 1) {
console.warn('Brightness control not available')
return
}
// Clamp value to valid range
const validLevel = Math.max(1, Math.min(maxLevel, Math.round(value)))
await setLevel(validLevel)
}A: Yes! Both platforms support full torch functionality including brightness control. iOS supports 10 discrete brightness levels on all devices with a torch. Android requires API 33+ (Android 13) and device support for brightness control.
A: Call getMaxLevel(). If it returns a number greater than 1, brightness control is supported. If it returns null or 1, only basic on/off is available.
const maxLevel = getMaxLevel()
const supportsBrightness = maxLevel !== null && maxLevel > 1A:
on()- Turns the torch on at default brightness (works on all devices)off()- Turns the torch offtoggle()- Toggles the torch between on and off statessetLevel(level)- Turns the torch on at a specific brightness level (Android 13+ only, device must support it)
A: No! Neither iOS nor Android require any permissions for using the flashlight/torch:
- iOS: No permissions required
- Android: No permissions required since API 23 (Android 6.0) -
CameraManager.setTorchMode()works without CAMERA permission
A: This can happen for several reasons:
- Your Android version is below 13 (API 33)
- Your device's camera flash doesn't support variable brightness (
FLASH_INFO_STRENGTH_MAXIMUM_LEVEL <= 1) - The camera hasn't been initialized yet (call it after torch is ready)
A:
getMaxLevel()orgetMaxLevel(false)- returns the hardware maximum (always 10 on iOS)getMaxLevel(true)- returns the current available level based on thermal state- Under normal conditions: 10
- Under thermal stress: may be lower (e.g., 8, 5, 3)
- Use this to detect thermal throttling and adjust your UI accordingly
On Android, the dynamic parameter is ignored and always returns the hardware maximum.
A: Yes, the torch will stay on even if your app goes to the background. However, it's recommended to turn it off when the app is backgrounded to save battery.
A: The library automatically turns off the torch when the module is disposed. However, you should also handle it in your component cleanup:
useEffect(() => {
return () => off()
}, [off])A: Thanks to NitroModules, this library has:
- Zero bridge overhead (direct C++/native communication)
- Type-safe APIs generated from native specs
- Real-time native callbacks for state changes
- Modern architecture designed for React Native's new architecture
A: Make sure you've installed both packages:
npm install react-native-torch-nitro react-native-nitro-modulesAnd run pod install on iOS:
cd ios && pod installThis repository includes a complete example app demonstrating all features of react-native-torch-nitro.
Location: The example app is located in the example/ directory within this repository.
# Clone the repository
git clone https://github.com/irekrog/react-native-torch-nitro.git
cd react-native-torch-nitro
# Install library dependencies
npm install
# Navigate to example app
cd example
npm install
# iOS
cd ios && pod install && cd ..
npm run ios
# Android
npm run androidThe example app demonstrates:
- Basic torch on/off control
- Toggle functionality
- Brightness control with slider (iOS and Android 13+)
- Real-time state and level tracking
- Error handling for unsupported features
- Modern UI with visual feedback
See the contributing guide to learn how to contribute to the repository and the development workflow.
MIT
Made with NitroModules ⚡️