Skip to content

Keep awake

@symbiote-native/keep-awake wraps expo-keep-awake — keeping the screen on for as long as a tag holds an active lock — so every SymbioteNative adapter can reach it, not just React. Like network, it mixes stateless imperative functions (activateKeepAwakeAsync/deactivateKeepAwake/isAvailableAsync/addListener) with a lifecycle hook/composable/rune/service (useKeepAwake) that activates a lock on mount and deactivates it on unmount — the tag-generation and activate/deactivate lifecycle is written once and shared by all four adapters.

OS platform Support
iOS ✅ live
Android ✅ live
Framework adapter Support
React ✅ live
Vue ✅ live
Angular ✅ live
Svelte ✅ live
Terminal window
npm install @symbiote-native/keep-awake

expo-keep-awake and expo-modules-core come along as regular dependencies, pinned to exact versions — never install either yourself, and never add the expo meta-package to your project (it bundles its own Metro/Babel pipeline, which conflicts with this project’s own).

No platform permission string is needed — keeping the screen awake has no runtime permission prompt on either platform.

Keep the screen awake for a component’s lifetime

Section titled “Keep the screen awake for a component’s lifetime”

useKeepAwake activates a keep-awake lock on mount and deactivates it on unmount — the simplest way to use this package.

import { Text } from '@symbiote-native/react';
import { useKeepAwake } from '@symbiote-native/keep-awake/react';
export default function KeepAwakeScreen() {
useKeepAwake(); // screen stays on for as long as this component is mounted
return <Text>Screen will not sleep</Text>;
}

The functions below work identically on every adapter — call them straight from any event handler when you need manual control over a specific tag, instead of the automatic mount/unmount lifecycle above.

import { Button } from '@symbiote-native/react';
import { activateKeepAwakeAsync, deactivateKeepAwake } from '@symbiote-native/keep-awake/react';
export default function ManualKeepAwake() {
return (
<>
<Button title="Keep awake" onPress={() => activateKeepAwakeAsync('download-tag')} />
<Button title="Allow sleep" onPress={() => deactivateKeepAwake('download-tag')} />
</>
);
}
Signature Description
isAvailableAsync(): Promise<boolean> Resolves whether the keep-awake API is available on this device
activateKeepAwakeAsync(tag?: string): Promise<void> Activates a keep-awake lock under tag (the shared default tag when none is given) — the screen stays on for as long as any tag holds an active lock
deactivateKeepAwake(tag?: string): Promise<void> Releases the keep-awake lock held under tag (the shared default tag when none is given)
addListener(tagOrListener: string | KeepAwakeListener, listener?: KeepAwakeListener): EventSubscription Subscribes to keep-awake state changes for a tag — overloaded to accept a bare listener for the default tag. Throws if the native module lacks addListenerForTag

Plus the ExpoKeepAwakeTag constant (the shared default tag string, 'ExpoKeepAwakeDefaultTag').

Field Type Default Description
tag (first argument) string | undefined a per-instance auto-generated tag The lock tag to activate/deactivate. React derives its default from useId(); Vue, Angular, and Svelte fall back to a monotonically-incrementing module-local counter (keep-awake-tag-1, keep-awake-tag-2, …), since none of them has a useId equivalent
options (second argument) KeepAwakeOptions | undefined undefined { listener?, suppressDeactivateWarnings? } — see below
Field Type Description
listener KeepAwakeListener | undefined Registered via addListener once activation resolves, for keep-awake state-change notifications
suppressDeactivateWarnings boolean | undefined When true, a rejected deactivateKeepAwake() call on unmount is silently swallowed instead of surfacing as an unhandled rejection
Field Type Description
state unknown Kept as a minimal placeholder — upstream’s own state shape is a web-only WakeLockSentinel-derived value with no native analogue, and native listeners for it rarely fire

KeepAwakeListener is (event: KeepAwakeEvent) => void.

@symbiote-native/keep-awake ships zero React/Vue/Angular logic in expo-keep-awake itself — its functions and types are hand-ported, verbatim, into this package’s own core/, resolving the native module through expo-modules-core’s requireNativeModule rather than the expo meta-package this project never installs:

packages/keep-awake/src/
├── core/ isAvailableAsync/activateKeepAwakeAsync/deactivateKeepAwake/addListener,
│ ExpoKeepAwakeTag. native-module.ts resolves the native module through
│ expo-modules-core's requireNativeModule. types.ts —
│ KeepAwakeEvent/KeepAwakeListener/KeepAwakeOptions, hand-ported from
│ KeepAwake.types.ts.
├── react/hooks/ @symbiote-native/keep-awake/react — useKeepAwake
├── vue/composables/ @symbiote-native/keep-awake/vue — useKeepAwake (same name)
├── svelte/runes/ @symbiote-native/keep-awake/svelte — useKeepAwake (same name)
└── angular/services/ @symbiote-native/keep-awake/angular — KeepAwakeService (`.connect()`,
no return value)

Each adapter’s hook/composable/rune/service is a thin lifecycle wrapper over the same core functions — activate on mount, register the optional listener once activation resolves, deactivate on unmount — written once as a shared pattern and reapplied identically across React’s useEffect, Vue’s onMounted/onUnmounted, Svelte’s $effect, and Angular’s effect() (the same connect()/effect() pattern battery’s LowPowerModeService uses, minus a Signal to return since keep-awake is a pure side effect). The native code itself is never vendored or copied — expo-modules-autolinking resolves it straight out of node_modules (see the native setup guide).