In Flutter, performing tasks in the background periodically can be crucial for various applications, such as fetching updates, syncing data, or performing maintenance. While Flutter itself doesn’t provide a direct solution for periodic background tasks due to platform constraints (Android and iOS), you can achieve this functionality by leveraging platform-specific APIs with the help of Flutter plugins and native code integration.
Why Schedule Periodic Background Tasks?
- Data Synchronization: Regularly sync data between the app and a remote server.
- Content Updates: Fetch the latest content without requiring user interaction.
- Notifications: Send scheduled reminders or notifications.
- Maintenance: Perform periodic cleanup tasks.
Methods for Scheduling Periodic Background Tasks
There are several methods to achieve periodic background task scheduling in Flutter:
- Using
workmanagerPlugin: For reliable, periodic tasks (especially on Android), use theworkmanagerplugin, which leverages Android’s WorkManager API. - Using
flutter_background_servicePlugin: Provides background execution capabilities suitable for simpler tasks. - Platform-Specific Code: Directly integrate with platform APIs like AlarmManager (Android) or BGTaskScheduler (iOS).
Method 1: Using the workmanager Plugin
The workmanager plugin is suitable for reliable and deferrable background tasks. It ensures that tasks run even if the app is closed or the device is idle.
Step 1: Add the workmanager Dependency
In your pubspec.yaml file, add the following dependency:
dependencies:
flutter:
sdk: flutter
workmanager: ^0.5.1
Then, run flutter pub get.
Step 2: Configure the workmanager
In your main Dart file (e.g., main.dart), configure and initialize the workmanager plugin. Make sure to perform all tasks that can be executed in the background without UI interaction, as direct UI manipulation isn’t allowed.
import 'dart:async';
import 'dart:isolate';
import 'package:flutter/material.dart';
import 'package:workmanager/workmanager.dart';
import 'package:shared_preferences/shared_preferences.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 completes. Optional
);
runApp(MyApp());
}
/// The callbackDispatcher is required. It is the entry point when running in Background.
@pragma('vm:entry-point') // Mandatory if the App is obfuscated or using Flutter 3.1+
void callbackDispatcher() {
Workmanager().executeTask((task, inputData) async {
switch (task) {
case "simplePeriodicTask":
final prefs = await SharedPreferences.getInstance();
final key = DateTime.now().toString();
await prefs.setString(key, DateTime.now().toString());
print('Native: periodic task is running: $key');
break;
}
return Future.value(true);
});
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Workmanager Example App'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
ElevatedButton(
onPressed: () {
Workmanager().registerPeriodicTask(
"simplePeriodicTask",
"Periodic task",
frequency: Duration(minutes: 15),
);
print('Periodic task registered');
},
child: const Text('Register Periodic Task'),
),
ElevatedButton(
onPressed: () async {
Workmanager().cancelByUniqueName("simplePeriodicTask");
print('Periodic task cancelled');
},
child: const Text('Cancel Periodic Task'),
),
],
),
),
),
);
}
}
Step 3: Register Periodic Task
You can register the periodic task within the Flutter app. Ensure that you provide a unique name and define the frequency of the task.
ElevatedButton(
onPressed: () {
Workmanager().registerPeriodicTask(
"simplePeriodicTask",
"Periodic task",
frequency: Duration(minutes: 15), // Task will repeat every 15 minutes
);
print('Periodic task registered');
},
child: const Text('Register Periodic Task'),
),
The task will now repeat every 15 minutes. Remember, Workmanager may not execute the task exactly at the specified time due to OS optimizations but it will attempt to do so as closely as possible.
Step 4: Handle Task Execution in callbackDispatcher
The callbackDispatcher is a top-level function that handles the execution of the background task. It’s mandatory for tasks registered via Workmanager.
@pragma('vm:entry-point')
void callbackDispatcher() {
Workmanager().executeTask((task, inputData) async {
switch (task) {
case "simplePeriodicTask":
// Perform your background task here
final prefs = await SharedPreferences.getInstance();
final key = DateTime.now().toString();
await prefs.setString(key, DateTime.now().toString());
print('Native: periodic task is running: $key');
break;
}
return Future.value(true);
});
}
Ensure that any function or logic within the callback dispatcher can operate without a Flutter context. Accessing SharedPreferences is a common use-case example. Be mindful of error handling to prevent task failures.
Method 2: Using the flutter_background_service Plugin
The flutter_background_service plugin is suitable for continuous background tasks or tasks that need to run more frequently than Workmanager allows (minimum of 15 minutes for WorkManager).
Step 1: Add the flutter_background_service Dependency
In your pubspec.yaml file, add the following dependency:
dependencies:
flutter:
sdk: flutter
flutter_background_service: ^2.0.0
Run flutter pub get.
Step 2: Configure the Background Service
In your main.dart, initialize and configure the background service:
import 'dart:async';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_background_service/flutter_background_service.dart';
import 'package:flutter_background_service_android/flutter_background_service_android.dart';
import 'package:shared_preferences/shared_preferences.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
await initializeService();
runApp(const MyApp());
}
Future initializeService() async {
final service = FlutterBackgroundService();
await service.configure(
androidConfiguration: AndroidConfiguration(
onStart: onStart,
autoStart: true,
isForegroundMode: true,
),
iosConfiguration: IosConfiguration(
autoStart: true,
onForeground: onStart,
onBackground: onIosBackground,
),
);
service.startService();
}
@pragma('vm:entry-point')
Future onIosBackground(ServiceInstance service) async {
WidgetsFlutterBinding.ensureInitialized();
return true;
}
@pragma('vm:entry-point')
void onStart(ServiceInstance service) async {
DartPluginRegistrant.ensureInitialized();
SharedPreferences prefs = await SharedPreferences.getInstance();
Timer.periodic(const Duration(seconds: 10), (timer) async {
final key = DateTime.now().toString();
await prefs.setString(key, DateTime.now().toString());
print('Background Service: tick: $key');
if (service is AndroidServiceInstance) {
if (await service.isForegroundMode()) {
service.setForegroundServiceEvent(
title: "Flutter Background Service",
content: "Updated at ${DateTime.now()}",
);
}
}
});
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State createState() => _MyAppState();
}
class _MyAppState extends State {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Background Service Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
FlutterBackgroundService().invoke("setAsForeground");
},
child: const Text("Set as foreground"),
),
ElevatedButton(
onPressed: () {
FlutterBackgroundService().invoke("setAsBackground");
},
child: const Text("Set as background"),
),
ElevatedButton(
onPressed: () async {
final service = FlutterBackgroundService();
bool isRunning = await service.isRunning();
if (isRunning) {
service.invoke("stopService");
} else {
service.startService();
}
setState(() {});
},
child: const Text(
"Stop Background Service",
),
),
],
),
),
),
);
}
}
Key points:
- Initialize the service by calling
FlutterBackgroundService()and configure its settings. - Android and iOS configurations are specified via
AndroidConfigurationandIosConfiguration, respectively. - The
onStartfunction defines what happens when the service starts. It must be annotated with@pragma('vm:entry-point'). - Use a
Timer.periodicto execute code at a specified interval within theonStartmethod. - For foreground services, you can set foreground service events with titles and content, providing updates visible in the notification tray.
Step 3: Implement the Background Logic in onStart
Implement your periodic background task within the onStart function:
@pragma('vm:entry-point')
void onStart(ServiceInstance service) async {
DartPluginRegistrant.ensureInitialized();
SharedPreferences prefs = await SharedPreferences.getInstance();
Timer.periodic(const Duration(seconds: 10), (timer) async {
final key = DateTime.now().toString();
await prefs.setString(key, DateTime.now().toString());
print('Background Service: tick: $key');
if (service is AndroidServiceInstance) {
if (await service.isForegroundMode()) {
service.setForegroundServiceEvent(
title: "Flutter Background Service",
content: "Updated at ${DateTime.now()}",
);
}
}
});
}
The example above uses a Timer.periodic to execute a task every 10 seconds. Ensure the logic you implement here doesn’t block the main thread or perform UI operations.
Platform-Specific Considerations
- Android: On newer versions of Android, background execution is heavily restricted to save battery life. Consider using foreground services for critical tasks or guiding the user on how to whitelist your app from battery optimizations.
- iOS: Background tasks are strictly managed. You may need to use
BGTaskSchedulerfor tasks that can be deferred and follow Apple’s guidelines for background execution.
Testing Background Tasks
Testing background tasks can be challenging due to OS restrictions and deferred execution. Consider these tips:
- Logging: Use logging extensively to track execution and identify issues.
- Foreground Services: Utilize foreground services during development to ensure your task runs reliably for debugging.
- Device Testing: Test on real devices to simulate real-world conditions.
- Battery Optimization: Check how your app behaves when battery optimization is enabled.
Conclusion
Scheduling periodic background tasks in Flutter requires leveraging platform-specific capabilities through plugins like workmanager and flutter_background_service or platform APIs. Understanding platform limitations and carefully handling background execution restrictions are crucial for creating reliable background tasks. Properly configured periodic tasks can significantly enhance the functionality and user experience of Flutter applications, providing seamless background updates and operations.