npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

expo-ios-background-task

v0.1.1

Published

An iOS-only module that exposes beginBackgroundTask and endBackgroundTask to JavaScript, enabling short, system-approved background execution time for finishing important work.

Readme

expo-ios-background-task

An iOS-only Expo module that exposes beginBackgroundTask and endBackgroundTask to JavaScript, enabling short, system-approved background execution time for finishing important work.

GitHub stars npm version npm downloads License: MIT

Overview

This module provides a JavaScript interface to iOS's background task API (UIApplication.beginBackgroundTask). It allows your app to request additional time to complete critical tasks when transitioning to the background, such as:

  • Saving user data
  • Uploading/downloading files
  • Completing network requests
  • Performing cleanup operations

Important: iOS provides a limited amount of background execution time (typically 30 seconds). You must end the task promptly when finished, or the system will terminate your app.

Platform Support

| Platform | Support | |----------|---------| | iOS | ✅ Full support | | Android | ⚠️ No-op (returns success, but doesn't actually run background tasks) | | Web | ⚠️ No-op (returns success, but doesn't actually run background tasks) |

Installation

Managed Expo Projects

npx expo install expo-ios-background-task

Bare React Native Projects

  1. Install the package:
npm install expo-ios-background-task
  1. For iOS, install CocoaPods dependencies:
cd ios && pod install && cd ..

Usage

Basic Example (Recommended Pattern)

According to Apple's documentation: Start the background task BEFORE beginning your long-running work, not when the app goes to background.

import ExpoIosBackgroundTask from 'expo-ios-background-task';

// ✅ CORRECT: Start task BEFORE beginning work
async function saveUserData() {
  // Begin the background task FIRST
  const result = await ExpoIosBackgroundTask.beginBackgroundTask('Save Data');
  
  if (!result.success) {
    console.error('Failed to start background task:', result.error);
    return;
  }

  const taskId = result.taskId;

  try {
    // Now perform your long-running work
    await performDatabaseSave();
    await syncWithServer();
    await uploadPendingFiles();
  } catch (error) {
    console.error('Error during background work:', error);
  } finally {
    // Always end the task when done
    await ExpoIosBackgroundTask.endBackgroundTask(taskId);
  }
}

// Trigger the function when needed (e.g., user action, app state change)
saveUserData();

Monitoring App State Changes

While you should start the background task before beginning work, you can use AppState to detect when the app is about to go to background and trigger your work:

import { AppState } from 'react-native';
import ExpoIosBackgroundTask from 'expo-ios-background-task';

// Listen for app state changes
AppState.addEventListener('change', async (nextAppState) => {
  // When app is about to go inactive/background
  if (nextAppState === 'inactive' || nextAppState === 'background') {
    await saveUserData(); // This function starts the task internally
  }
});

async function saveUserData() {
  // Start background task BEFORE doing work
  const result = await ExpoIosBackgroundTask.beginBackgroundTask('Save Data');
  
  if (result.success) {
    const taskId = result.taskId;
    
    try {
      // Perform your work here
      await syncData();
    } finally {
      await ExpoIosBackgroundTask.endBackgroundTask(taskId);
    }
  }
}

❌ Incorrect Pattern (Don't Do This)

// DON'T: Wait until app is in background to start task
AppState.addEventListener('change', async (nextAppState) => {
  if (nextAppState === 'background') {
    // ❌ This is too late - work has already started or app is already backgrounded
    const result = await ExpoIosBackgroundTask.beginBackgroundTask('Save Data');
    // ... do work
  }
});

With Expiration Handler

import ExpoIosBackgroundTask, { useEvent } from 'expo-ios-background-task';

function MyComponent() {
  const [taskId, setTaskId] = useState<number | null>(null);

  // Listen for task expiration events
  const expirationPayload = useEvent(ExpoIosBackgroundTask, 'onTaskExpiration');

  useEffect(() => {
    if (expirationPayload) {
      console.warn(`Task "${expirationPayload.taskName}" expired!`);
      
      // Clean up and end the task
      if (taskId === expirationPayload.taskId) {
        setTaskId(null);
      }
    }
  }, [expirationPayload, taskId]);

  const performLongTask = async () => {
    // Start task BEFORE beginning work
    const result = await ExpoIosBackgroundTask.beginBackgroundTask('My Task');
    
    if (result.success) {
      setTaskId(result.taskId);
      
      try {
        // Do your long-running work
        await doSomethingImportant();
      } finally {
        await ExpoIosBackgroundTask.endBackgroundTask(result.taskId);
        setTaskId(null);
      }
    }
  };

  return (
    <Button title="Start Long Task" onPress={performLongTask} />
  );
}

Check Remaining Background Time

import ExpoIosBackgroundTask from 'expo-ios-background-task';

const remaining = ExpoIosBackgroundTask.getBackgroundTimeRemaining();

if (remaining === -1) {
  console.log('App is in foreground (unlimited time)');
} else {
  console.log(`Remaining background time: ${remaining.toFixed(2)} seconds`);
}

API Reference

Methods

beginBackgroundTask(taskName?: string): Promise<BeginBackgroundTaskResult>

Begins a background task that extends your app's runtime. Returns a task ID that must be used to end the task.

Important: Call this method BEFORE starting any long-running task, not after the app goes to background.

Parameters:

  • taskName (optional): A string identifier for the task. Useful for debugging and logging.

Returns:

{
  success: boolean;
  taskId: number;      // Use this ID to end the task
  taskName?: string;   // The name you provided
  error?: string;      // Error message if success is false
}

Example:

const result = await ExpoIosBackgroundTask.beginBackgroundTask('Upload Photo');

if (result.success) {
  console.log(`Task started with ID: ${result.taskId}`);
  
  try {
    // Do your work...
    await uploadPhoto();
  } finally {
    await ExpoIosBackgroundTask.endBackgroundTask(result.taskId);
  }
} else {
  console.error(`Failed to start task: ${result.error}`);
}

Possible Errors:

  • "Failed to begin background task. App may not be in background state or maximum tasks reached." - You've reached the maximum number of concurrent background tasks (typically 1-2).

endBackgroundTask(taskId: number): Promise<boolean>

Ends a background task that was previously started. Always call this when your work is complete to free system resources.

Parameters:

  • taskId: The task ID returned from beginBackgroundTask.

Returns:

  • Promise<boolean>: true if the task was ended successfully, false if the task doesn't exist or has already expired.

Example:

const success = await ExpoIosBackgroundTask.endBackgroundTask(taskId);

if (!success) {
  console.warn('Task may have already expired');
}

getBackgroundTimeRemaining(): number

Gets the amount of background time remaining for the app.

Returns:

  • number: The remaining background time in seconds, or -1 if the app is in the foreground.

Example:

const remaining = ExpoIosBackgroundTask.getBackgroundTimeRemaining();

if (remaining === -1) {
  console.log('App is in foreground');
} else if (remaining < 5) {
  console.warn('Less than 5 seconds remaining!');
}

Events

onTaskExpiration

Fired when a background task is about to expire. The system calls this when it's about to terminate your app.

Event Payload:

{
  taskId: number;
  taskName: string;
}

Example:

import { useEvent } from 'expo';
import ExpoIosBackgroundTask from 'expo-ios-background-task';

function MyComponent() {
  const expirationPayload = useEvent(ExpoIosBackgroundTask, 'onTaskExpiration');

  useEffect(() => {
    if (expirationPayload) {
      console.warn(`Task "${expirationPayload.taskName}" is expiring!`);
      
      // End the task immediately to avoid app termination
      ExpoIosBackgroundTask.endBackgroundTask(expirationPayload.taskId);
    }
  }, [expirationPayload]);
}

TypeScript Support

This package includes TypeScript definitions. Import types as needed:

import ExpoIosBackgroundTask, {
  BeginBackgroundTaskResult,
  TaskExpirationEventPayload
} from 'expo-ios-background-task';

Important Notes

⚠️ When to Start Background Tasks

Apple's Recommendation: Start the background task BEFORE beginning your long-running work, not when the app transitions to background.

From Apple's documentation:

"Don't wait until your app moves to the background to call the beginBackgroundTask(withName:expirationHandler:) method. Call the method before performing any long-running task."

// ✅ CORRECT
async function saveData() {
  const task = await ExpoIosBackgroundTask.beginBackgroundTask('Save');
  try {
    await performSave(); // Start work AFTER beginning task
  } finally {
    await ExpoIosBackgroundTask.endBackgroundTask(task.taskId);
  }
}

// ❌ INCORRECT
async function saveData() {
  await performSave(); // Work started before task begins
  const task = await ExpoIosBackgroundTask.beginBackgroundTask('Save');
  await ExpoIosBackgroundTask.endBackgroundTask(task.taskId);
}

⚠️ Background Time Limits

  • iOS typically provides ~30 seconds of background execution time
  • The exact time varies based on system conditions and battery level
  • Always end tasks promptly to avoid app termination

⚠️ Task Limits

  • iOS allows a limited number of concurrent background tasks (typically 1-2)
  • Starting a new task when at the limit will fail
  • Always end tasks when finished

⚠️ Always End Tasks

  • Critical: Always call endBackgroundTask when your work is complete
  • Use try/finally blocks to ensure tasks are ended even if errors occur
  • If a task expires, the system will end it automatically, but your app may be terminated

Troubleshooting

Task fails to start

  • Check task limit: You may have reached the maximum number of concurrent tasks
  • Verify iOS platform: This module only works on iOS
  • System resources: iOS may deny background tasks under low memory or battery conditions

Task expires immediately

  • Check remaining time: Use getBackgroundTimeRemaining() to see available time
  • System conditions: iOS may provide less time under certain conditions (low battery, etc.)

Module not found error

  • Rebuild native code: Run npx expo prebuild --clean and rebuild your app
  • Check installation: Ensure the package is installed and linked correctly
  • Development builds: Make sure you're using a development build, not Expo Go (Expo Go doesn't support custom native modules)

Example App

See the example app for a complete working example with:

  • Task management UI
  • Timer demonstration
  • App state monitoring
  • Expiration handling

Show Your Support

If you find this package helpful, please consider giving it a ⭐️ on GitHub!

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

MIT © Prathamesh Karambelkar

Links


Made with ❤️ by Prathamesh Karambelkar