Background Tasks in React Native 2026: What Still Breaks on iOS and Android
Background execution is where cross-platform promises go to die. Here's what actually runs on iOS 18 and Android 15 in 2026, where Expo helps, and when you have to drop into Swift or Kotlin.

Background execution is the quiet graveyard of cross-platform promises. A feature that works perfectly in the simulator will silently stop firing on a real device three days after install, and nobody will know until a user emails support. Here's what actually runs in React Native and Expo on iOS 18 and Android 15 in 2026 — and where you'll still end up writing Swift or Kotlin.
Why background work is the hardest part of mobile
Both platforms spent the last five years tightening the screws on what apps can do when the user isn't looking. iOS treats background CPU as a privilege the system grants based on usage patterns it won't document. Android's Doze, App Standby Buckets, and background execution limits mean a job you scheduled for "every 15 minutes" might fire once a day on a phone that's been in a pocket.
That matters for React Native because the JavaScript thread, by default, is not alive when your app is backgrounded. Anything that needs to run — a sync, a location fetch, a receipt validation — has to be scheduled through a native scheduler and either executed natively or wake up a headless JS context.
There are, broadly, four buckets of background work you'll want:
- Periodic sync (fetch new data every N minutes/hours)
- Deferred one-shot jobs (upload this video when wifi is back)
- Location / geofencing (fire when the user enters a region)
- Long-running foreground services (navigation, audio, fitness tracking)
Each has a different story on iOS vs Android, and a different story in Expo vs bare React Native.
Periodic sync: expo-background-task and its limits
Expo's old expo-background-fetch module is deprecated in favor of expo-background-task, which wraps BGTaskScheduler on iOS and WorkManager on Android. If you've been avoiding this upgrade because "it worked fine", upgrade anyway — the new API models the asymmetry between the platforms more honestly.
import * as BackgroundTask from 'expo-background-task';
import * as TaskManager from 'expo-task-manager';
const SYNC_TASK = 'app.sync.inbox';
TaskManager.defineTask(SYNC_TASK, async () => {
try {
const result = await syncInbox();
return result.hadNewData
? BackgroundTask.BackgroundTaskResult.Success
: BackgroundTask.BackgroundTaskResult.Success;
} catch (e) {
return BackgroundTask.BackgroundTaskResult.Failed;
}
});
export async function registerSync() {
await BackgroundTask.registerTaskAsync(SYNC_TASK, {
minimumInterval: 15 * 60, // seconds — treated as a hint
});
}
The word to internalize is hint. On iOS, BGAppRefreshTask fires when the OS decides your app has earned it, based on how often the user opens the app, battery state, network, and Low Power Mode. In our experience with mid-engagement consumer apps, this means somewhere between a few times a day and once every two days. On Android, WorkManager is more honest about honoring intervals, but Doze on an idle device will still batch and defer.
If your product requires freshness on a predictable cadence, periodic background tasks are not your answer. Silent push notifications are — more on that below.
When to use silent push instead
For "wake the app to pull new data" use cases, a silent push (content-available: 1 on iOS, data-only message on FCM) gives you a server-controlled trigger. It's not a guarantee — iOS throttles aggressively, and if the user force-quit your app from the switcher, nothing will wake it — but it's usually better than hoping the OS schedules you.
Pair silent push with a background task as a fallback. Don't rely on either one alone.
Deferred one-shot jobs: upload queues done right
This is where React Native developers most often reach for a hack and regret it. The pattern: user records a video, taps post, backgrounds the app before upload finishes. You want the upload to continue.
The only reliable answer on iOS is URLSession with a background configuration. fetch and axios will not survive backgrounding past the ~30 second grace period. On Android, you need either a WorkManager job with a Worker that does the upload in native code, or a foreground service with a persistent notification.
For React Native, this almost always means a native module. react-native-background-upload and its forks handle the plumbing, but check maintenance status before you commit — this is an area where libraries die quietly. We've shipped our own thin wrappers around URLSessionUploadTask and WorkManager more than once when the ecosystem options were stale.
If you're on Expo managed workflow, this is one of the first features that will push you toward a config plugin and a custom native module. That's fine — EAS Build makes it workable — but plan for it.
Geofencing and location: the one place Expo really shines
expo-location combined with expo-task-manager gives you one of the better cross-platform geofencing stories available. The task runs even when the app is terminated (within platform limits), and the API is the same on both sides.
import * as Location from 'expo-location';
import * as TaskManager from 'expo-task-manager';
const GEOFENCE_TASK = 'app.geofence';
TaskManager.defineTask(GEOFENCE_TASK, ({ data, error }) => {
if (error) return;
const { eventType, region } = data as any;
if (eventType === Location.GeofencingEventType.Enter) {
// enqueue a notification or sync
}
});
await Location.startGeofencingAsync(GEOFENCE_TASK, [
{ identifier: 'office', latitude: 52.23, longitude: 21.01, radius: 150 },
]);
Caveats worth knowing before you ship:
- iOS caps you at 20 active geofences per app. Design around it — swap regions as the user moves.
- On Android 14+, you need
ACCESS_BACKGROUND_LOCATIONand a clear rationale, and Play review will scrutinize it. Have a privacy-policy page and an in-app disclosure ready. - Terminated-state wakeups are reliable on iOS, less so on aggressive OEM Android skins (Xiaomi, Huawei, some OnePlus). Users on those devices will need to whitelist your app in battery settings. There is no API fix for this.
Long-running foreground work: when you need a native module
Turn-by-turn navigation, workout tracking, audio playback, and VoIP all need genuine long-running execution. On iOS you declare the appropriate UIBackgroundModes; on Android you run a foreground service with a visible notification. Neither is something you can bolt on from JavaScript alone.
For audio, react-native-track-player is still the pragmatic choice. For fitness-style continuous location, you'll likely write a Kotlin foreground service and bridge it. For VoIP, use CallKit on iOS and ConnectionService on Android via a dedicated library — don't try to wing it.
The honest Swift/Kotlin tradeoff
If 80% of your background behavior is one specific long-running feature — say, a run tracker — writing the native pieces in Swift and Kotlin and treating React Native as the UI layer is often the lower-pain path. You get platform-idiomatic lifecycle handling and you stop fighting the JS bridge for a feature that doesn't need it. We've reviewed codebases where three generations of "background location libraries" were layered on top of each other; the cleanup was always to write ~200 lines of native code and delete the libraries.
That doesn't mean abandoning React Native. It means recognizing that background execution is one of the few places where native code is genuinely less work than cross-platform code.
Testing and the "it works on my device" trap
Background tasks will lie to you in development. Both platforms give you debug hooks that bypass the real scheduler:
- iOS:
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"your.task.id"]from the LLDB console while paused. - Android:
adb shell cmd jobscheduler run -f <package> <jobId>for WorkManager jobs.
Use these, but also test on a real device for at least a week with the app backgrounded, battery at various levels, and the device idle overnight. Instrument your task handlers with analytics events (task_fired, task_completed, task_failed) so you can see real-world firing rates in production. The gap between simulator behavior and a user's phone on day 14 is the whole story.
Also: force-quitting the app from the switcher kills most background behavior on both platforms. iOS treats this as "the user doesn't want this app running" and will stop scheduling background refresh entirely. There is nothing you can do about this. Document it for support.
Where we'd start
If you're adding background work to a React Native app today, we'd sequence it like this:
- Write down which bucket your feature falls into — periodic, deferred, location, or long-running. Don't skip this; it determines everything.
- For periodic freshness, lead with silent push and treat
expo-background-taskas a backup, not the primary mechanism. - For uploads, budget for a native module from day one. Don't ship
fetchin asetTimeoutand hope. - For geofencing, use
expo-location+expo-task-manager, and build the OEM battery-whitelist conversation into your onboarding for Android power users. - For anything long-running, write the native service in Swift or Kotlin and bridge it. You'll ship faster and sleep better.
And instrument everything. The only way to know if background work is actually happening in production is to count it. If you want a hand untangling an existing background mess, that's exactly the kind of work our mobile team does.
Want a team like ours?
72Technologies builds production software for the kind of teams who actually read this blog.
Start a projectKeep reading

Surviving App Store Review in 2026: The React Native Rejections We Keep Seeing
A field guide to the App Store and Play Store rejections that hit React Native and Expo apps hardest in 2026 — and how to ship a build that actually gets through on the first try.

Push Notifications in React Native 2026: Expo Notifications vs FCM vs OneSignal in Production
We've shipped push on all three stacks — Expo Notifications, raw FCM/APNs, and OneSignal. Here's how they actually behave in production, including the token-refresh bugs nobody warns you about.
EAS Update in Production: What OTA Actually Ships (and What It Can't)
OTA updates are the killer feature of Expo, until they aren't. Here's what EAS Update can and cannot ship in 2026, how to roll back safely, and where we've been burned in production.
