Skip to content

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
Terminal window
npm install @symbiote-native/background-fetch @symbiote-native/task-manager

Scaffolding 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 adapter
import { 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 it

Identical import surface on every adapter — @symbiote-native/background-fetch/react, /vue, /svelte, /solid, /angular all re-export the same 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
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
  • registerTaskAsync requires the task to already be defined — it throws if the task named isn’t defined yet with @symbiote-native/task-manager’s defineTask.
  • getStatusAsync resolves Available on Android without calling native at all — Android has no status concept of its own; the native call exists only on iOS.
  • setMinimumIntervalAsync silently 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.

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.