Implementing Background Fetch in Flutter Applications

Background fetch is a crucial feature for modern mobile applications that require periodic data synchronization without user intervention. It enables applications to retrieve data in the background, even when the app is not actively running, ensuring that users always have access to the latest information when they open the app. In this comprehensive guide, we will explore how to implement background fetch in Flutter applications.

Understanding Background Fetch

Background fetch allows your Flutter application to periodically wake up in the background and execute code to fetch new data. This is useful for scenarios such as:

  • Updating content for news apps
  • Syncing data for productivity tools
  • Fetching new messages for messaging apps

Why Implement Background Fetch?

  • Improved User Experience: Keeps data up-to-date, even when the app isn’t running.
  • Efficient Data Usage: Retrieves data periodically in small chunks, optimizing battery and data consumption.
  • Enhanced App Engagement: Provides timely and relevant content, increasing user engagement and retention.

Steps to Implement Background Fetch in Flutter

Implementing background fetch in Flutter involves using platform-specific APIs. Here are the steps you need to follow:

Step 1: Add Dependencies

First, you need to add the workmanager package to your pubspec.yaml file. This package provides a high-level API to schedule background tasks on Android and iOS.

dependencies:
  flutter:
    sdk: flutter
  workmanager: ^0.5.0 # Use the latest version

Run flutter pub get to install the dependency.

Step 2: Configure Android

For Android, you need to make the following configurations:

Modify AndroidManifest.xml

Add the necessary permissions and service definitions in your android/app/src/main/AndroidManifest.xml file:


    
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    <uses-permission android:name="android.permission.WAKE_LOCK"/>
    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:name="io.flutter.app.FlutterApplication"
        android:label="your_app"
        android:icon="@mipmap/ic_launcher">
        
        <activity
            android:name=".MainActivity"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <meta-data
                android:name="io.flutter.embedding.android.NormalTheme"
                android:resource="@style/NormalTheme"
                />
            <meta-data
                android:name="io.flutter.embedding.android.SplashScreenDrawable"
                android:resource="@drawable/launch_background"
                />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <service
            android:name="dev.fluttercommunity.workmanager.ApplicationRestarter"
            android:exported="false"
            android:permission="android.permission.BIND_JOB_SERVICE"/>
        <receiver android:name="dev.fluttercommunity.workmanager.BootReceiver"
            android:exported="false">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
            </intent-filter>
        </receiver>
    </application>
</manifest>

Ensure that the package name com.example.your_app is replaced with your actual package name.

Initialize Workmanager

In your main.dart file, initialize the Workmanager:

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

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  Workmanager().initialize(
    callbackDispatcher,
    isInDebugMode: true,
  );
  Workmanager().registerPeriodicTask(
    "periodicTaskIdentifier",
    "simplePeriodicTask",
    frequency: Duration(minutes: 15),
  );
  runApp(MyApp());
}

void callbackDispatcher() {
  Workmanager().executeTask((task, inputData) {
    switch (task) {
      case 'simplePeriodicTask':
        print("[Native Runner] Running background task");
        // Perform your background task here, e.g., fetch data, update local storage
        _fetchData();
        break;
    }
    return Future.value(true);
  });
}

Future _fetchData() async {
  // Replace this with your actual background task logic
  await Future.delayed(Duration(seconds: 5));
  print('Background data fetch completed');
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Background Fetch Demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Background Fetch Demo'),
        ),
        body: Center(
          child: Text('App Running'),
        ),
      ),
    );
  }
}

The callbackDispatcher function is a top-level function that Workmanager uses to execute background tasks. Ensure this function is not inside any class.

Step 3: Configure iOS

For iOS, you need to configure the Info.plist file and handle the background fetch in your app delegate.

Modify Info.plist

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

<key>UIBackgroundModes</key>
<array>
    <string>fetch</string>
    <string>processing</string>
</array>
Handle Background Fetch in App Delegate

You don’t need additional code for iOS with the workmanager package as it handles the platform-specific details. Just ensure you’ve configured the Info.plist correctly.

Step 4: Test Background Fetch

Testing background fetch can be tricky. Here’s how you can test it on both platforms:

Android

You can simulate background fetch using the following ADB command:

adb shell cmd appops set your_app_package_name RUN_IN_BACKGROUND allow
adb shell cmd appops set your_app_package_name WAKE_LOCK allow
adb shell am set-inactive your_app_package_name true
adb shell cmd jobscheduler run your_app_package_name

Replace your_app_package_name with your application’s package name.

iOS

In Xcode, you can simulate background fetch by selecting Debug > Simulate Background Fetch.

Advanced Configuration

Periodic Tasks

Use Workmanager().registerPeriodicTask to schedule periodic tasks:

Workmanager().registerPeriodicTask(
  "periodicTaskIdentifier",
  "simplePeriodicTask",
  frequency: Duration(minutes: 15),
);

Ensure the frequency is set according to your app’s needs and the platform’s limitations (e.g., iOS has constraints on how frequently background fetch can occur).

One-Off Tasks

For tasks that need to run only once, use Workmanager().registerOneOffTask:

Workmanager().registerOneOffTask(
  "oneOffTaskIdentifier",
  "simpleOneOffTask",
);
Input Data

You can pass data to your background tasks using the inputData parameter:

Workmanager().registerPeriodicTask(
  "periodicTaskIdentifier",
  "simplePeriodicTask",
  inputData: {'key': 'value'},
  frequency: Duration(minutes: 15),
);

void callbackDispatcher() {
  Workmanager().executeTask((task, inputData) {
    String? value = inputData?['key'];
    print("Received data: $value");
    return Future.value(true);
  });
}

Best Practices for Background Fetch

  • Minimize Data Usage: Fetch only necessary data to conserve battery and bandwidth.
  • Handle Errors: Implement proper error handling and retry mechanisms for failed tasks.
  • Respect Battery Life: Avoid frequent background fetches that can drain the battery quickly.
  • Monitor Performance: Use analytics to monitor the performance and impact of background fetch on your app.
  • Test Thoroughly: Test background fetch on various devices and network conditions to ensure reliability.

Troubleshooting

  • Tasks Not Running: Ensure that you have configured all the necessary permissions and settings for both Android and iOS.
  • Callback Not Called: Verify that the callbackDispatcher function is correctly implemented and not inside any class.
  • Data Not Updating: Check your background task logic for any errors and ensure that data is being fetched and stored correctly.

Conclusion

Implementing background fetch in Flutter applications is essential for providing up-to-date data and enhancing the user experience. By following the steps outlined in this guide, you can effectively integrate background fetch into your Flutter apps, ensuring they remain relevant and engaging, even when running in the background. Always adhere to best practices to optimize battery life and data usage, providing a seamless experience for your users.