← Back to posts Cover image for Flutter Background Tasks: Mastering `workmanager` for iOS and Android

Flutter Background Tasks: Mastering `workmanager` for iOS and Android

· 5 min read
Weekly Digest

The Flutter news you actually need

No spam, ever. Unsubscribe in one click.

Chris
By Chris

Keep Your Flutter App Working Behind the Scenes: A Guide to workmanager

You’ve built a fantastic Flutter app. It works perfectly when it’s open. But what about when it’s not? Many apps need to perform tasks in the background—syncing data, sending notifications, or processing information—even when the user has moved on to another activity. Implementing reliable background processing can feel daunting, especially with the differing restrictions imposed by iOS and Android. This is where the workmanager plugin shines, providing a unified API to schedule background tasks. However, getting it to work consistently, particularly on iOS, requires careful setup. Let’s master it together.

Understanding the Challenge

Background execution is a privilege, not a right. Mobile operating systems aggressively manage battery life and resources.

  • Android is relatively permissive, allowing for periodic tasks, but with restrictions on execution time and frequency, especially on newer versions.
  • iOS is far more restrictive. True background execution is generally reserved for specific use cases like audio, location, or VoIP. For most other tasks, iOS allows you to schedule a “background fetch” or use “background processing,” but the OS decides when (or even if) to run your code, often batching tasks to conserve power.

The workmanager plugin abstracts these complexities. It uses WorkManager on Android and BGTaskScheduler on iOS under the hood. The key to success is configuring each platform correctly.

Setting Up workmanager in Your Flutter Project

First, add the dependency to your pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter
  workmanager: ^0.5.1 # Check for the latest version on pub.dev

Run flutter pub get.

1. The Core Dart Initialization

You need to define a top-level function that will serve as the entry point for your background tasks. This function must be outside of any class and set up before runApp.

Important: This function must be a top-level or static method. It cannot be an instance method of a class.

// This is your background task callback
@pragma('vm:entry-point') // This annotation is crucial for Flutter's AOT compilation
void executeTask(String taskName, Map<String, dynamic>? inputData) {
  Workmanager().executeTask((task, inputData) async {
    print("🎯 [Background Task] Executing: $task");

    switch (task) {
      case 'simplePeriodicTask':
        // Your background logic here
        final DateTime now = DateTime.now();
        print('Syncing data at ${now.toString()}');
        // Perhaps call an API or process local DB
        return Future.value(true);
      case 'rescheduledTask':
        // You can reschedule tasks or handle specific logic
        print('Processing user data: ${inputData?['userId']}');
        return Future.value(true);
      default:
        print("Unknown task: $task");
        return Future.value(false);
    }
  });
}

void main() {
  // Initialize the workmanager before runApp
  Workmanager().initialize(
    executeTask, // The top-level function defined above
    isInDebugMode: false, // Set to true for more verbose logs during development
  );

  // Optionally, schedule a task when the app starts
  Workmanager().registerPeriodicTask(
    'syncDataTask',
    'simplePeriodicTask',
    frequency: Duration(hours: 1), // Minimum is 15 minutes on Android
    constraints: Constraints(
      networkType: NetworkType.connected, // Requires network
    ),
  );

  runApp(MyApp());
}

2. Platform-Specific Configuration

This is where most developers stumble. You must edit the native project files.

Android (android/app/src/main/AndroidManifest.xml): Add permissions inside the <manifest> tag and register the plugin’s service inside the <application> tag.

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- Optional: Add permissions your task might need -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />

    <application
        android:name=".Application"
        ...>
        <!-- Register the WorkManager plugin's service -->
        <service
            android:name="com.beaconsmith.workmanager.WorkmanagerBackgroundService"
            android:enabled="true"
            android:exported="false" />
    </application>
</manifest>

iOS (ios/Runner/Info.plist): You must declare the background modes your app uses. For workmanager, this is typically “Background fetch.”

  1. Open ios/Runner/Info.plist in Xcode (right-click -> Open As -> Source Code).
  2. Add the following key just before the final </dict>:
<key>UIBackgroundModes</key>
<array>
  <string>fetch</string>
  <!-- Add other modes like 'location' or 'processing' if needed -->
</array>
  1. Critical iOS Step: Enable Background Capabilities.
    • Open your ios folder in Xcode (open ios/Runner.xcworkspace).
    • Select the Runner target.
    • Go to the “Signing & Capabilities” tab.
    • Click the ”+” button and add the “Background Modes” capability.
    • Check the box for “Background fetch.”

Common Pitfalls and Troubleshooting

  • “Task not executing on iOS”: This is the most common issue. Double-check the Info.plist entry and the Background Modes capability in Xcode. Remember, iOS simulates background fetches in the simulator infrequently. Always test on a real device. You can trigger a fetch manually in Xcode via Debug -> Simulate Background Fetch.
  • Task runs in debug but not in release: Ensure your top-level callback function is annotated with @pragma('vm:entry-point'). This prevents the Dart compiler from tree-shaking it away in release mode.
  • Android tasks stop after app termination: On some OEMs (Xiaomi, Huawei, etc.), you must manually grant “Auto-start” permission to your app in the device settings to allow background work after a force stop.
  • Frequency is not respected: The OS is the final arbiter. On both platforms, especially iOS, your frequency is a request, not a guarantee. The OS will batch and delay tasks to optimize battery.

Best Practices

  1. Keep Tasks Short and Efficient: Background execution windows are limited (often 30 seconds or less). Do the minimum required work and finish.
  2. Use Constraints Wisely: Schedule tasks to run only when conditions are met (e.g., networkType: NetworkType.connected, requiresBatteryNotLow: true). This prevents wasted cycles and improves user battery life.
  3. Handle Initialization Errors: Wrap your Workmanager().initialize() call in a try-catch block. A misconfiguration on one platform shouldn’t crash your whole app.
  4. Test Rigorously on Both Platforms: Background behavior differs vastly. Develop a testing routine that involves running your app, putting it in the background, and monitoring logs over an extended period.

By following this guide, you’ll move past the frustration of silent background failures. The workmanager plugin, when properly configured, becomes a reliable tool for keeping your app’s data fresh and responsive, providing a seamless experience for your users no matter what they’re doing on their device.

This blog is produced with the assistance of AI by a human editor. Learn more

Related Posts

Cover image for Optimizing Flutter UI Performance: Best Practices for Date Formatting and Expensive Operations

Optimizing Flutter UI Performance: Best Practices for Date Formatting and Expensive Operations

Developers often face performance bottlenecks when performing expensive operations like date formatting directly within Flutter's `build` method, especially in fast-scrolling lists. This post will delve into common pitfalls, explain why these operations are costly, and provide practical strategies for optimizing UI performance by caching formatters, using `initState`, and leveraging `compute` for background processing without blocking the UI.

Cover image for Optimizing Your Flutter Dev Setup: IDEs, Simulators, and AI Tools for Peak Productivity

Optimizing Your Flutter Dev Setup: IDEs, Simulators, and AI Tools for Peak Productivity

Flutter developers frequently seek to refine their development environments. This post will dive into popular IDE choices like VS Code and Android Studio, discuss best practices for managing iOS and Android simulators (including in-IDE options), and explore the practical integration of AI tools for code generation and problem-solving to boost overall efficiency.

Cover image for Demystifying Flutter Performance: Practical Strategies for Large-Scale Apps

Demystifying Flutter Performance: Practical Strategies for Large-Scale Apps

Flutter's performance is often blamed for issues in complex applications, but the real culprits are usually architectural decisions, inefficient widget rebuilds, and unoptimized resource handling. This post will dive into common performance bottlenecks in large Flutter apps, providing actionable strategies for profiling, optimizing state management, handling images and network requests efficiently, and leveraging CI/CD for continuous performance monitoring.