Using the workmanager Package for Background Processing in Flutter

In Flutter, background processing is crucial for executing tasks without interrupting the user interface (UI). The workmanager package is a popular choice for scheduling and managing background tasks efficiently. This article delves into how to effectively use the workmanager package in Flutter for background processing.

What is Background Processing in Flutter?

Background processing refers to running tasks outside the main thread, ensuring the UI remains responsive. Common use cases include:

  • Syncing data
  • Downloading files
  • Processing images
  • Performing scheduled updates

Why Use workmanager for Background Processing?

The workmanager package provides a robust and reliable way to handle background tasks because:

  • It integrates with Android’s WorkManager and iOS’s BGTaskScheduler.
  • It supports both one-off and periodic tasks.
  • It handles device reboots and app restarts gracefully.
  • It offers flexible constraints like network connectivity and device charging.

How to Implement Background Processing with workmanager in Flutter

To use the workmanager package, follow these steps:

Step 1: Add workmanager Dependency

Add the workmanager package to your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  workmanager: ^0.5.1

Run flutter pub get to install the dependency.

Step 2: Configure the Native Platforms

For Android and iOS, additional configuration is required.

Android Configuration
  1. Update AndroidManifest.xml:

Add the following to your android/app/src/main/AndroidManifest.xml file:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="your.package.name">
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    <application
        android:name="io.flutter.app.FlutterApplication"
        android:label="your_app_name"
        android:icon="@mipmap/ic_launcher">
        <!-- Add this receiver -->
        <receiver
            android:name="dev.fluttercommunity.workmanager.FlutterWorkerBootReceiver"
            android:exported="false">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
                <action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
            </intent-filter>
        </receiver>
        <activity
            android:name=".MainActivity"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <!-- ... -->
        </activity>
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
    </application>
</manifest>
  1. Update build.gradle:

Ensure your android/build.gradle file has the necessary settings:

buildscript {
    ext.kotlin_version = '1.7.1'
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath("com.android.tools.build:gradle:7.0.0")
        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
    }
}
iOS Configuration
  1. Background Modes:

In Xcode, enable Background Modes for your project. Go to your target’s Signing & Capabilities tab, click + Capability, and add Background Modes. Check Background fetch and Background processing.

Background Modes Configuration

  1. Update Info.plist:

Add the following key to your ios/Runner/Info.plist file:

<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
    <string>your.app.identifier.task</string>
</array>

Replace your.app.identifier.task with a unique identifier for your background task.

Step 3: Initialize Workmanager

In your Flutter app, initialize the Workmanager in the main function before running the app:

import 'package:flutter/material.dart';
import 'package:workmanager/workmanager.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  Workmanager().initialize(
    callbackDispatcher, // The top level function, aka callbackDispatcher
    isInDebugMode: true, // If enabled it will post a notification whenever the work is running. Optional.
  );
  Workmanager().registerOneOffTask(
      "task-identifier",
      "simpleTask",
      initialDelay: Duration(seconds: 5),
      inputData: {'key': 'value'}); //android only

  runApp(MyApp());
}

void callbackDispatcher() {
  Workmanager().executeTask((task, inputData) {
    switch (task) {
      case 'simpleTask':
        print("[Flutter] Native platform is running task simpleTask");
        // Do something (e.g. sync data, download files)
        break;
    }
    return Future.value(true);
  });
}


class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Workmanager Example'),
        ),
        body: Center(
          child: Text('Running background task...'),
        ),
      ),
    );
  }
}

Step 4: Define the Callback Dispatcher

The callbackDispatcher function is crucial. It is a top-level function that WorkManager uses to find and execute tasks.


void callbackDispatcher() {
  Workmanager().executeTask((task, inputData) {
    switch (task) {
      case 'simpleTask':
        print("[Flutter] Native platform is running task simpleTask");
        // Do something (e.g. sync data, download files)
        break;
    }
    return Future.value(true);
  });
}

Step 5: Register a Task

You can register either a one-off task or a periodic task. Here’s how to register a one-off task:

Workmanager().registerOneOffTask(
  "task-identifier",
  "simpleTask",
  initialDelay: Duration(seconds: 5),
  inputData: {'key': 'value'} // Android-specific input data
);

For periodic tasks:

Workmanager().registerPeriodicTask(
  "periodic-task-identifier",
  "periodicTask",
  initialDelay: Duration(minutes: 15), // First execution
  frequency: Duration(hours: 1), // Recurring every hour
  inputData: {'key': 'periodic value'} // Android-specific input data
);

Step 6: Handle Constraints (Optional)

You can add constraints to your tasks to specify when they should run. For example, run only when the device is connected to the internet:

Workmanager().registerOneOffTask(
  "task-with-constraints",
  "constrainedTask",
  constraints: Constraints(
    requiresNetworkType: NetworkType.connected,
    requiresCharging: true,
  )
);

Best Practices for Using workmanager

  • Keep Tasks Short: Background tasks should be lightweight and complete quickly.
  • Handle Errors: Implement robust error handling to manage failures gracefully.
  • Battery Efficiency: Minimize the impact on battery life by avoiding unnecessary tasks.
  • User Awareness: Provide feedback to the user about background activity.

Example Use Case: Periodic Data Sync

Consider an app that needs to periodically sync user data with a server.

void syncData() {
  // Logic to sync data with the server
  print('Syncing data in the background...');
}

void callbackDispatcher() {
  Workmanager().executeTask((task, inputData) {
    switch (task) {
      case 'syncTask':
        syncData();
        break;
    }
    return Future.value(true);
  });
}

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  Workmanager().initialize(
    callbackDispatcher,
    isInDebugMode: true,
  );
  Workmanager().registerPeriodicTask(
    "sync-data-task",
    "syncTask",
    frequency: Duration(hours: 6), // Sync every 6 hours
  );
  runApp(MyApp());
}

Conclusion

The workmanager package offers a reliable solution for managing background tasks in Flutter. By following the steps and best practices outlined in this article, you can efficiently schedule and execute tasks, ensuring your app remains responsive and effective.