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
callbackDispatcherfunction 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.