# Background task

> Run deferrable work in the background on the OS's own schedule (BGTaskScheduler, WorkManager) on every SymbioteNative adapter.

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`](https://github.com/expo/expo/tree/main/packages/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`](/docs/packages/background-fetch/). It does not define what
the task does: define it first with
[`@symbiote-native/task-manager`](/docs/packages/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

```sh
npm install @symbiote-native/background-task @symbiote-native/task-manager
```

Scaffolding 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`](https://github.com/OneEyed1366/symbiote-native/tree/master/packages/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.

<Aside type="danger" title="Native setup is required before first use">
  Follow [How to: wire up an Expo native
  module](/docs/howtos/expo-native-module-setup/) once per app first. iOS
  additionally needs `UIBackgroundModes: processing` and
  `BGTaskSchedulerPermittedIdentifiers` in `Info.plist`, both wired
  automatically by the same postinstall step. Android needs no manual step.
</Aside>

<Aside type="caution" title="The iOS Simulator has no BGTaskScheduler support">
  Apple's own limitation — physical device only. `getStatusAsync` reads
  `Restricted` on a simulator and `registerTaskAsync` is a no-op there
  regardless of `Info.plist`. Test registration on a real device.
</Aside>

## Usage

```ts
// index.ts, alongside AppRegistry.registerComponent — identical on every adapter
import { 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 });
```

```ts
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 it
```

Identical import surface on every adapter — `@symbiote-native/background-task/react`, `/vue`,
`/svelte`, `/solid`, `/angular` all re-export the same functions.

## API

### 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`

| 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 |

### Enums

| Enum                   | Values                                                                                           |
| ---------------------- | ------------------------------------------------------------------------------------------------ |
| `BackgroundTaskResult` | What the task executor returns: `Success` or `Failed`                                            |
| `BackgroundTaskStatus` | `Restricted` (unavailable, for example on an iOS Simulator) or `Available`                       |

## Notes

- **`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`.
- **The iOS Simulator cannot run it.** It has no `BGTaskScheduler` support, so the status reads
  `Restricted` and registration is skipped. Test on a device.
- **`registerTaskAsync` is a no-op, twice over.** It skips silently (one-time console warning)
  when the status reads `Restricted`, and it skips again, quietly, when the task is already
  registered.
- **`triggerTaskWorkerForTestingAsync` only runs in a dev build** — it always resolves `false` in
  production.

## Common questions

- **Nothing runs on the iOS Simulator.** Background tasks need a physical device.
- **`triggerTaskWorkerForTestingAsync` does nothing on iOS.** `BGTaskSchedulerPermittedIdentifiers`
  must contain `com.expo.modules.backgroundtask.processing` in 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](https://docs.expo.dev/versions/latest/sdk/background-task/),
[expo/expo#40440](https://github.com/expo/expo/issues/40440),
[expo/expo#48786](https://github.com/expo/expo/issues/48786),
[expo/expo#35350](https://github.com/expo/expo/pull/35350).
