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/primitive/service (useKeepAwake/createKeepAwake) 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 five adapters.
| OS platform | Support |
|---|---|
| iOS | ✅ live |
| Android | ✅ live |
| Framework adapter | Support |
|---|---|
| React | ✅ live |
| Vue | ✅ live |
| Angular | ✅ live |
| Svelte | ✅ live |
| Solid | ✅ live |
Installation
Section titled “Installation”npm install @symbiote-native/keep-awakeScaffolding or extending a SymbioteNative app? npx @symbiote-native/cli new --keep-awake (or
add --keep-awake in an existing app) installs and wires this for you — see
@symbiote-native/cli.
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 { 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>;}<script setup lang="ts">import { useKeepAwake } from '@symbiote-native/keep-awake/vue';
useKeepAwake();</script>
<template> <text>Screen will not sleep</text></template>import { Component, inject } from '@angular/core';import { SYMBIOTE_ELEMENTS } from '@symbiote-native/angular';import { KeepAwakeService } from '@symbiote-native/keep-awake/angular';
@Component({ standalone: true, imports: [SYMBIOTE_ELEMENTS], template: `<text>Screen will not sleep</text>`,})export class KeepAwakeScreen { constructor() { inject(KeepAwakeService).connect(); }}KeepAwakeService.connect() has no return value — it’s a pure side effect for the component’s
lifetime, wired through Angular’s effect() so activation/deactivation follows the same
mount/cleanup timing as React’s useEffect/Vue’s onMounted/onUnmounted.
<script lang="ts"> import { useKeepAwake } from '@symbiote-native/keep-awake/svelte';
useKeepAwake(); // screen stays on for as long as this component is mounted</script>
<text>Screen will not sleep</text>useKeepAwake has no return value here either — it’s a pure $effect side effect wired to
the component’s mount/unmount, same as Vue’s onMounted/onUnmounted pair.
import { createKeepAwake } from '@symbiote-native/keep-awake/solid';
export default function KeepAwakeScreen() { createKeepAwake(); // screen stays on for as long as this owner is alive
return <text>Screen will not sleep</text>;}createKeepAwake has no return value - the body runs once, so activation happens
synchronously and release happens via onCleanup, matching Vue’s onMounted/onUnmounted
pair with no separate mount hook.
Imperative functions
Section titled “Imperative functions”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 { 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')} /> </> );}<script setup lang="ts">import { activateKeepAwakeAsync, deactivateKeepAwake } from '@symbiote-native/keep-awake/vue';</script>
<template> <button title="Keep awake" @press="activateKeepAwakeAsync('download-tag')" /> <button title="Allow sleep" @press="deactivateKeepAwake('download-tag')" /></template>import { Component } from '@angular/core';import { SYMBIOTE_ELEMENTS } from '@symbiote-native/angular';import { activateKeepAwakeAsync, deactivateKeepAwake } from '@symbiote-native/keep-awake/angular';
@Component({ standalone: true, imports: [SYMBIOTE_ELEMENTS], template: ` <button title="Keep awake" (press)="activateKeepAwakeAsync('download-tag')" /> <button title="Allow sleep" (press)="deactivateKeepAwake('download-tag')" /> `,})export class ManualKeepAwake { protected readonly activateKeepAwakeAsync = activateKeepAwakeAsync; protected readonly deactivateKeepAwake = deactivateKeepAwake;}<script lang="ts"> import { activateKeepAwakeAsync, deactivateKeepAwake } from '@symbiote-native/keep-awake/svelte';</script>
<button title="Keep awake" onPress={() => activateKeepAwakeAsync('download-tag')} /><button title="Allow sleep" onPress={() => deactivateKeepAwake('download-tag')} />import { activateKeepAwakeAsync, deactivateKeepAwake } from '@symbiote-native/keep-awake/solid';
export default function ManualKeepAwake() { return ( <> <button title="Keep awake" onPress={() => activateKeepAwakeAsync('download-tag')} /> <button title="Allow sleep" onPress={() => deactivateKeepAwake('download-tag')} /> </> );}Functions
Section titled “Functions”| 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').
useKeepAwake() config
Section titled “useKeepAwake() config”| 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, Svelte, and Solid 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 |
KeepAwakeOptions
Section titled “KeepAwakeOptions”| 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 |
KeepAwakeEvent / KeepAwakeListener
Section titled “KeepAwakeEvent / KeepAwakeListener”| 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.
Common questions
Section titled “Common questions”- I use two tags and the screen sleeps after releasing one (Android). Reported upstream: on Android releasing one tag can let the device sleep while another is still active, unlike iOS. Prefer a single tag per screen.
- The screen still turns off. On Android it sets
FLAG_KEEP_SCREEN_ON, which system power saving can override. Test a release build too, since some reports are dev-only. - It conflicts with another library. Another library clearing the same window flag can undo it.
Sources: Expo docs: KeepAwake, expo/expo#6031, expo/expo#6324, expo/expo#24007, expo/expo#4885.
How the wrapper works
Section titled “How the wrapper works”@symbiote-native/keep-awake ships zero React/Vue/Angular/Svelte/Solid 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)├── solid/primitives/ @symbiote-native/keep-awake/solid — createKeepAwake└── angular/services/ @symbiote-native/keep-awake/angular — KeepAwakeService (`.connect()`, no return value)Each adapter’s hook/composable/rune/primitive/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, Solid’s synchronous body +
onCleanup, 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).