react-native-device-wake-state
v1.0.2
Published
React Native lock / screen-off detection plus keep-awake. Reports PowerManager + Keyguard on Android and iOS protected-data / lock notifications. Also blocks idle sleep (FLAG_KEEP_SCREEN_ON / idleTimerDisabled).
Maintainers
Readme
react-native-device-wake-state
React Native lock / screen-off detection. AppState misses many OEM lock-button cases; this plugin reports native wake and keyguard flags.
npm: react-native-device-wake-state
GitHub: Management-AND-Computer-Consultants/mcc-react-native-device-wake-state
Native module: NativeModules.DeviceWakeState
| Platform | Mechanism | Host wiring |
|----------|-----------|-------------|
| Android | ACTION_SCREEN_OFF / SCREEN_ON / USER_PRESENT plus PowerManager.isInteractive and KeyguardManager | Autolink only |
| iOS | Protected-data unavailable/available plus app + UIScene background / foreground / active | pod install only |
No extra Android permissions. Android and iOS both autolink — no MainActivity hooks, no Xcode Compile Sources, no Info.plist keys.
Table of contents
- Install
- Architecture
- Package layout
- JS usage
- API
- Android
- iOS
- Event payload
- Session guard
- Native method map
- Notes
1. Install
cd your-react-native-app
npm install react-native-device-wake-statepackage.json:
"react-native-device-wake-state": "^1.0.2"GitHub install (optional):
npm install github:Management-AND-Computer-Consultants/mcc-react-native-device-wake-stateThen rebuild native on both platforms (Metro reload is not enough):
# Android
npx react-native run-android
# iOS (pod install autolinks DeviceWakeState.h / DeviceWakeState.m)
cd ios && pod install && cd .. && npx react-native run-iosAutolink registers:
- Android:
DeviceWakeStatePackage - iOS: CocoaPods target
react-native-device-wake-state
Do not also register an in-app DeviceWakeStatePackage in MainApplication, and do not keep DeviceWakeState.m in the app Xcode Compile Sources. Two modules with the same name will clash.
Local checkout for edits: D:\CoDe\RNPlugin\react-native-device-wake-state. Push to GitHub, then reinstall the GitHub spec in the app.
2. Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Host JS │
│ getDeviceWakeState() │
│ subscribeDeviceWakeState(event => …) │
│ subscribeSessionGuard({ onInterrupted, onResumed… }) │
└───────────────────────────────┬─────────────────────────────────┘
│ NativeModules.DeviceWakeState
│ event: DeviceWakeStateChange
┌───────────────────────────────▼─────────────────────────────────┐
│ Android — DeviceWakeStatePackage / Module (autolinked) │
│ BroadcastReceiver on reactApplicationContext │
│ SCREEN_OFF → screenOff │
│ SCREEN_ON → screenOn │
│ USER_PRESENT → userPresent │
│ getState: isInteractive, isKeyguardLocked, isDeviceLocked │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ iOS — DeviceWakeState.h / DeviceWakeState.m (autolinked pod) │
│ ProtectedData unavailable → screenOff │
│ ProtectedData available → userPresent │
│ DidEnterBackground / UISceneDidEnterBackground → screenOff │
│ WillEnterForeground / DidBecomeActive / UISceneDidActivate │
│ → screenOn │
└─────────────────────────────────────────────────────────────────┘3. Package layout
react-native-device-wake-state/
├── src/index.ts
├── android/
│ ├── build.gradle
│ └── src/main/java/com/reactnativedevicewakestate/
│ ├── DeviceWakeStatePackage.kt
│ └── DeviceWakeStateModule.kt
├── ios/
│ ├── DeviceWakeState.h
│ └── DeviceWakeState.m
├── react-native.config.js
├── react-native-device-wake-state.podspec
└── README.md4. JS usage
Same API on Android and iOS.
Keep screen on (idle sleep)
Replaces @sayem314/react-native-keep-awake. Does not intercept the lock button.
import {
activateKeepAwake,
deactivateKeepAwake,
} from 'react-native-device-wake-state';
useEffect(() => {
activateKeepAwake();
return () => deactivateKeepAwake();
}, []);One-shot poll
import { getDeviceWakeState, isLockedOrScreenOff } from 'react-native-device-wake-state';
const state = await getDeviceWakeState();
if (isLockedOrScreenOff(state)) {
// screen off or keyguard showing
}Native events
import { subscribeDeviceWakeState } from 'react-native-device-wake-state';
useEffect(() => {
return subscribeDeviceWakeState(event => {
console.log(event.type, event.isKeyguardLocked);
});
}, []);Subscribe only while you need it (for example while recording). Always unsubscribe so Android does not leak the receiver.
Session guard (lock then unlock)
import {
subscribeSessionGuard,
} from 'react-native-device-wake-state';
import {
isPictureInPictureActive,
isPictureInPictureEnabled,
} from 'react-native-picture-in-picture';
useEffect(() => {
if (!isRecording) {
return;
}
return subscribeSessionGuard({
onForeground: () => {
activateKeepAwake();
},
onInterrupted: () => {
wentToBackgroundRef.current = true;
},
onResumedAfterInterrupt: () => {
// show "keep the screen unlocked" alert
},
isPipEnabled: isPictureInPictureEnabled,
isPipActive: isPictureInPictureActive,
});
}, [isRecording]);isPipEnabled / isPipActive are optional. Pass them so Home → Picture-in-Picture is not treated as a lock.
Alias: subscribeRecordingSessionGuard is the same function.
5. API
| Export | Description |
|--------|-------------|
| getDeviceWakeState() | Promise<IDeviceWakeState \| null> |
| startListening() / stopListening() | Native receiver / observers |
| activateKeepAwake() / deactivateKeepAwake() | Block idle sleep (FLAG_KEEP_SCREEN_ON / idleTimerDisabled). JS re-applies every 3 s. |
| subscribeDeviceWakeState(listener) | Event subscription; returns unsubscribe |
| isLockedOrScreenOff(state) | !interactive \|\| keyguard \|\| deviceLocked |
| subscribeSessionGuard(options) | AppState + native events + 2 s poll |
Default export: { getState, startListening, stopListening, subscribe, subscribeSessionGuard, isLockedOrScreenOff }.
6. Android
Autolink is enough. There is no MainActivity host class (unlike Picture-in-Picture).
| Piece | What happens |
|--------|----------------|
| DeviceWakeStatePackage | Registered from react-native.config.js |
| DeviceWakeStateModule | NativeModules.DeviceWakeState |
| Receiver | reactApplicationContext (survives activity pause / lock) |
| API 33+ | Context.RECEIVER_NOT_EXPORTED |
| Permissions | None |
getState() reads:
PowerManager.isInteractiveKeyguardManager.isKeyguardLockedKeyguardManager.isDeviceLocked(API 22+)
Events:
| Broadcast | type |
|-----------|--------|
| ACTION_SCREEN_OFF | screenOff |
| ACTION_SCREEN_ON | screenOn |
| ACTION_USER_PRESENT | userPresent |
If you previously had in-app DeviceWakeStatePackage(), remove that add(...) from MainApplication after switching to this plugin.
7. iOS
Autolink is enough. No Xcode Compile Sources step and no Info.plist keys.
cd ios && pod installafter npm install.- CocoaPods compiles
ios/DeviceWakeState.h+ios/DeviceWakeState.m. - New Architecture (
RCT_NEW_ARCH_ENABLED=1) is supported via the React interop layer (RCTEventEmitter).
getState() reads:
UIApplication.isProtectedDataAvailable→ inverted asisKeyguardLocked/isDeviceLockedapplicationState != background && !locked→isInteractive
Observers (registered on startListening / NativeEventEmitter):
| Notification | type |
|-------------|--------|
| UIApplicationProtectedDataWillBecomeUnavailable | screenOff |
| UIApplicationProtectedDataDidBecomeAvailable | userPresent |
| UIApplicationDidEnterBackground / UISceneDidEnterBackground | screenOff |
| UIApplicationWillEnterForeground / UISceneWillEnterForeground | screenOn |
| UIApplicationDidBecomeActive / UISceneDidActivate | screenOn |
If you previously compiled Universal_App/DeviceWakeState.m inside the app target, remove it from the Xcode project after switching so the pod is the only DeviceWakeState module.
Devices without a passcode may keep isProtectedDataAvailable == YES while locked. The session guard still sees Home / lock via background + AppState.
8. Event payload
interface IDeviceWakeStateEvent {
type: 'screenOff' | 'screenOn' | 'userPresent';
isInteractive: boolean;
isKeyguardLocked: boolean;
isDeviceLocked: boolean;
}| type | Android | iOS |
|--------|--------|-----|
| screenOff | ACTION_SCREEN_OFF | protected data unavailable or enter background |
| screenOn | ACTION_SCREEN_ON | will enter foreground / did become active |
| userPresent | ACTION_USER_PRESENT | protected data available |
JS treats !isInteractive || isKeyguardLocked || isDeviceLocked as locked, even if type is screenOn (PIN can still be showing).
null from getDeviceWakeState is treated as not locked (fail open).
9. Session guard
subscribeSessionGuard combines:
- AppState —
backgroundmarks interrupt unless PiP is enabled;inactiveis ignored;activerunsonForeground - Native
DeviceWakeStateChange - Poll every 2 s (override with
pollMs) for OEM skins that drop a broadcast
onResumedAfterInterrupt runs only when interrupted is true, AppState is active, and getState() is not locked.
Always stopListening in the returned unsubscribe.
10. Native method map
| JS | Android | iOS |
|----|---------|-----|
| getState() | PowerManager + KeyguardManager | protected data + applicationState |
| activateKeepAwake() | FLAG_KEEP_SCREEN_ON | idleTimerDisabled = YES |
| deactivateKeepAwake() | clear FLAG_KEEP_SCREEN_ON | idleTimerDisabled = NO |
| startListening() | register BroadcastReceiver (RECEIVER_NOT_EXPORTED on API 33+) | registerObservers (app + UIScene) |
| stopListening() | unregister | removeObserver |
| addListener / removeListeners | RN stubs | RCTEventEmitter startObserving / stopObserving |
Event name: DeviceWakeStateChange.
On iOS, isKeyguardLocked and isDeviceLocked are the same boolean.
11. Notes
- Rebuild native after installing or changing this package.
- Do not also keep an in-app
DeviceWakeStatePackage/DeviceWakeState.min the host app. - Listen only while a session needs it. Do not call
startListeningfromApp.tsxor Splash. - Always unsubscribe; a leaked Android receiver keeps firing after the screen unmounts.
- PiP is not a lock. Pass
isPipEnabled/isPipActiveintosubscribeSessionGuard. inactiveis not an interrupt (permission sheets, PiP transitions).- Android 13+ must use
RECEIVER_NOT_EXPORTED(the plugin already does). - Register on
reactApplicationContext, not the activity — activity-scoped receivers die when the activity pauses (exactly when lock happens).
Checklist
package.jsonusesreact-native-device-wake-statefrom npm.- Native rebuild: Android
run-android, iOSpod installthenrun-ios. - Subscribe in a
useEffecttied to the session (isRecording), not app mount. - If you also use Picture-in-Picture, pass the PiP flags into
subscribeSessionGuard. - Remove in-app
DeviceWakeStateModule.kt/DeviceWakeStatePackage.kt/DeviceWakeState.mafter switching.
