Background task
Sync, clean up or upload while the app is closed, when the OS decides the moment is right.
@symbiote-native/background-task wraps
expo-background-task so
every SymbioteNative adapter can register a task that runs in the background on the OS’s own
schedule: BGTaskScheduler on iOS, WorkManager on Android. It is the modern replacement for
@symbiote-native/background-fetch. It does not define what
the task does: define it first with
@symbiote-native/task-manager’s defineTask, then register it
here.
Every export is a plain function or a one-time module-load side effect — no hook/composable/ service to wrap, so every adapter’s entry point is a plain re-export of the same core.
| 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/background-task @symbiote-native/task-managerScaffolding or extending a SymbioteNative app? npx @symbiote-native/cli new --background-task
(or add --background-task in an existing app) installs and wires this for you — see
@symbiote-native/cli.
expo-background-task and expo-modules-core come along as regular dependencies, pinned to exact
versions — never install them yourself, and never add the expo meta-package to your project.
// index.ts, alongside AppRegistry.registerComponent — identical on every adapterimport { defineTask } from '@symbiote-native/task-manager';import { registerTaskAsync, BackgroundTaskResult } from '@symbiote-native/background-task';
const SYNC_TASK = 'background-sync';
defineTask(SYNC_TASK, async () => { try { await runSync(); return BackgroundTaskResult.Success; } catch (error) { console.error('background-sync failed:', error); return BackgroundTaskResult.Failed; }});
// Somewhere after the task is defined (a settings screen, app bootstrap, …):await registerTaskAsync(SYNC_TASK, { minimumInterval: 15 });import { getStatusAsync, unregisterTaskAsync, addExpirationListener,} from '@symbiote-native/background-task';
const status = await getStatusAsync();
// iOS only — the system can interrupt a running background task before it finishes.const subscription = addExpirationListener(() => { console.warn('background-sync was interrupted before it finished');});subscription.remove();
await unregisterTaskAsync(SYNC_TASK); // stop receiving executions of itIdentical import surface on every adapter — @symbiote-native/background-task/react, /vue,
/svelte, /solid, /angular all re-export the same functions.
Functions
Section titled “Functions”| Signature | Description |
|---|---|
registerTaskAsync(taskName, options?): Promise<void> |
Registers a defined task. Throws if the task is not defined; skips if already registered |
unregisterTaskAsync(taskName): Promise<void> |
Stops executions of the task |
getStatusAsync(): Promise<BackgroundTaskStatus> |
Whether background tasks are available on this device |
addExpirationListener(listener): { remove } |
iOS only. Calls listener when the system interrupts a running task before it finishes |
triggerTaskWorkerForTestingAsync(): Promise<boolean> |
Runs the task worker now, for testing. Resolves false outside a dev build |
IBackgroundTaskOptions
Section titled “IBackgroundTaskOptions”| Field | Type | Description |
|---|---|---|
minimumInterval |
number | undefined |
Inexact interval in minutes between runs. Defaults to 12 hours; the minimum is 15 minutes. The OS treats it as a minimum delay only, and on iOS a short interval is often ignored |
| Enum | Values |
|---|---|
BackgroundTaskResult |
What the task executor returns: Success or Failed |
BackgroundTaskStatus |
Restricted (unavailable, for example on an iOS Simulator) or Available |
registerTaskAsyncrequires the task to already be defined — it throws if the task named isn’t defined yet with@symbiote-native/task-manager’sdefineTask.- The iOS Simulator cannot run it. It has no
BGTaskSchedulersupport, so the status readsRestrictedand registration is skipped. Test on a device. registerTaskAsyncis a no-op, twice over. It skips silently (one-time console warning) when the status readsRestricted, and it skips again, quietly, when the task is already registered.triggerTaskWorkerForTestingAsynconly runs in a dev build — it always resolvesfalsein production.
Common questions
Section titled “Common questions”- Nothing runs on the iOS Simulator. Background tasks need a physical device.
triggerTaskWorkerForTestingAsyncdoes nothing on iOS.BGTaskSchedulerPermittedIdentifiersmust containcom.expo.modules.backgroundtask.processingin Info.plist; rebuild after adding it.minimumInterval. A hint only; the OS decides when to run.- Status ignores the Background App Refresh setting on iOS. Reported upstream: the status call may not read the real permission, so do not rely on it alone.
Sources: Expo docs: BackgroundTask, expo/expo#40440, expo/expo#48786, expo/expo#35350.