Background fetch
Refresh data on a rough schedule while the app is not open. @symbiote-native/background-fetch
wraps expo-background-fetch
so every SymbioteNative adapter can register a task that fires periodically in the background. It
does not define what the task does: define it first with
@symbiote-native/task-manager’s defineTask, then register it
here for periodic execution.
Every export is a plain async function — 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-fetch @symbiote-native/task-managerScaffolding or extending a SymbioteNative app? npx @symbiote-native/cli new --background-fetch
(or add --background-fetch in an existing app) installs and wires this for you — see
@symbiote-native/cli.
expo-background-fetch 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, BackgroundFetchResult } from '@symbiote-native/background-fetch';
const SYNC_TASK = 'background-sync';
defineTask(SYNC_TASK, async ({ data, error }) => { if (error) { console.error('background-sync failed:', error); return BackgroundFetchResult.Failed; } const receivedNewData = await runSync(data); return receivedNewData ? BackgroundFetchResult.NewData : BackgroundFetchResult.NoData;});
// Somewhere after the task is defined (a settings screen, app bootstrap, …):await registerTaskAsync(SYNC_TASK, { minimumInterval: 900 });import { getStatusAsync, unregisterTaskAsync } from '@symbiote-native/background-fetch';
const status = await getStatusAsync();await unregisterTaskAsync(SYNC_TASK); // stop receiving background-fetch callbacks for itIdentical import surface on every adapter — @symbiote-native/background-fetch/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 to fire periodically. Throws if the task is not defined |
unregisterTaskAsync(taskName): Promise<void> |
Stops background-fetch callbacks for the task |
getStatusAsync(): Promise<BackgroundFetchStatus | null> |
Whether the app can receive background fetches right now |
setMinimumIntervalAsync(minimumInterval): Promise<void> |
Sets the minimum interval in seconds. A no-op on Android |
IBackgroundFetchOptions
Section titled “IBackgroundFetchOptions”| Field | Type | Description |
|---|---|---|
minimumInterval |
number | undefined |
Inexact interval in seconds between fetches. The OS may stretch it to save battery. Android defaults to 10 minutes; iOS uses its smallest supported interval (10 to 15 minutes) |
stopOnTerminate |
boolean | undefined |
Android only. Stop receiving events once the user terminates the app. Defaults to true |
startOnBoot |
boolean | undefined |
Android only. Restart events after the device finishes booting. Defaults to false |
| Enum | Values |
|---|---|
BackgroundFetchResult |
What the task executor returns, used by iOS to schedule future fetches: NoData, NewData, Failed. Android ignores it |
BackgroundFetchStatus |
Denied (user disabled background behavior), Restricted (unavailable, for example parental controls), Available |
registerTaskAsyncrequires the task to already be defined — it throws if the task named isn’t defined yet with@symbiote-native/task-manager’sdefineTask.getStatusAsyncresolvesAvailableon Android without calling native at all — Android has no status concept of its own; the native call exists only on iOS.setMinimumIntervalAsyncsilently no-ops on Android rather than throwing — there is no native equivalent call there.- Every function warns once, on first call, that this API is deprecated. It still works; the
warning is a nudge toward
@symbiote-native/background-task, not a functional restriction.
Common questions
Section titled “Common questions”The task never fires. The minimumInterval is a hint, not a promise. iOS decides when to run it
from usage patterns and may run it every half hour, every few hours, or not at all on a given day.
Also check that defineTask runs at module scope, outside any component, and that it ran before
registerTaskAsync.
registerTaskAsync is rejected on Android in a release build. This is reported upstream for
release builds only. Test a release build, and register after the task is defined.
Does it run when the user kills the app? On Android stopOnTerminate defaults to true, so it
stops. Set stopOnTerminate: false and startOnBoot: true to keep it going. iOS stops when the user
force-quits the app.
What should the task return? On iOS return BackgroundFetchResult.NewData, NoData or Failed,
so the system can schedule future fetches. Android ignores the value.
Should I use this for new code? No. Use background task, the modern replacement.
iOS needs a background mode. UIBackgroundModes: fetch is added for you through the task
manager’s link manifest.
Sources: Expo docs: BackgroundFetch, expo/expo#35551 registerTaskAsync rejected on Android, release build only, expo/expo#33596 background fetch does not execute the task on Android, expo/expo#36492 background task never executing.