Mobile applications often need to perform tasks in the background, even when the app is not in the foreground. These background tasks can include syncing data, uploading files, sending notifications, and more. Flutter provides several ways to implement background tasks and services, each with its own strengths and use cases.
Understanding Background Tasks and Services in Flutter
Background tasks and services in Flutter allow you to execute code without direct user interaction and while the app is not actively running. However, due to mobile operating system limitations (particularly on iOS and Android), managing these tasks requires careful consideration.
Why Use Background Tasks and Services?
- Data Synchronization: Keep the app’s data up-to-date by syncing with a remote server.
- Push Notifications: Receive and handle push notifications for timely updates.
- File Uploads: Upload large files in the background without blocking the UI.
- Location Tracking: Continuously track user location for specific application purposes.
- Periodic Tasks: Run maintenance tasks, such as clearing caches or logs.
Implementing Background Tasks in Flutter
Flutter offers several methods to implement background tasks, each suited for different scenarios:
1. Using compute
Function
The compute
function is useful for running CPU-intensive tasks in the background without blocking the main UI thread. It spawns an isolate, allowing parallel execution.
Step 1: Define a Background Task Function
import 'package:flutter/foundation.dart';
Future<String> performBackgroundTask(String message) async {
// Simulate a time-consuming task
await Future.delayed(Duration(seconds: 5));
return 'Task completed with message: $message';
}
Step 2: Call compute
in Your Widget
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Background Task Example'),
),
body: Center(
child: ElevatedButton(
onPressed: () async {
String result = await compute(performBackgroundTask, 'Hello from UI!');
print(result); // Output: Task completed with message: Hello from UI!
},
child: Text('Run Background Task'),
),
),
),
);
}
}
Future<String> performBackgroundTask(String message) async {
// Simulate a time-consuming task
await Future.delayed(Duration(seconds: 5));
return 'Task completed with message: $message';
}
In this example, tapping the button runs performBackgroundTask
in a separate isolate, preventing UI freezes.
2. Using Plugins for Periodic Tasks
For tasks that need to run periodically, Flutter provides plugins like flutter_background_service
.
Step 1: Add Dependencies
Include the flutter_background_service
package in your pubspec.yaml
file:
dependencies:
flutter_background_service: ^4.0.0
Step 2: Initialize 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';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await initializeService();
runApp(MyApp());
}
Future<void> initializeService() async {
final service = FlutterBackgroundService();
await service.configure(
androidConfiguration: AndroidConfiguration(
onStart: onStart,
autoStart: true,
isForegroundMode: true,
),
iosConfiguration: IosConfiguration(
autoStart: true,
onForeground: onStart,
onBackground: iosBackground,
),
);
service.startService();
}
// to ensure this is executed
// run before the app is terminated
@pragma('vm:entry-point')
Future<bool> iosBackground(ServiceInstance service) async {
WidgetsFlutterBinding.ensureInitialized();
DartPluginRegistrant.ensureInitialized();
return true;
}
@pragma('vm:entry-point')
void onStart(ServiceInstance service) async {
DartPluginRegistrant.ensureInitialized();
if (service is AndroidServiceInstance) {
service.on('setAsForeground').listen((event) {
service.setAsForegroundService();
});
service.on('setAsBackground').listen((event) {
service.setAsBackgroundService();
});
}
service.on('stopService').listen((event) {
service.stopSelf();
});
Timer.periodic(const Duration(seconds: 10), (timer) async {
if (service is AndroidServiceInstance) {
service.setForegroundNotificationInfo(
title: "Flutter Background Service",
content: "Updated at ${DateTime.now()}",
);
}
print('Background service running: ${DateTime.now()}');
});
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Background Service Example'),
),
body: Center(
child: Text('Flutter Background Service Example'),
),
),
);
}
}
Key aspects:
initializeService()
configures the background service with settings for both Android and iOS.onStart
defines the main logic that runs in the background. In this case, a timer that prints a message every 10 seconds and updates the foreground notification on Android.- Platform-specific configurations ensure compatibility with Android and iOS background execution requirements.
3. Using Workmanager
Workmanager
is a background processing library provided by Android Jetpack. It allows you to schedule deferrable, guaranteed background work.
Step 1: Add Workmanager Dependency
Include the flutter_workmanager
package in your pubspec.yaml
:
dependencies:
flutter_workmanager: ^0.5.1
Step 2: Initialize Workmanager
import 'package:flutter/material.dart';
import 'package:flutter_workmanager/flutter_workmanager.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await FlutterWorkmanager.initialize(
callbackDispatcher,
isInDebugMode: true,
);
runApp(MyApp());
}
void callbackDispatcher() {
Workmanager.executeTask((task, inputData) {
switch (task) {
case 'simpleTask':
print('Simple task executed: ${DateTime.now()}');
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: ElevatedButton(
onPressed: () {
FlutterWorkmanager.registerOneOffTask(
"simpleTask",
"simpleTask",
);
},
child: Text('Register One-Off Task'),
),
),
),
);
}
}
Explanation:
callbackDispatcher
is a top-level function that handles incoming work requests.FlutterWorkmanager.initialize
initializes the Workmanager.FlutterWorkmanager.registerOneOffTask
registers a task namedsimpleTask
to be executed once.
4. Firebase Cloud Messaging (FCM) for Notifications
Firebase Cloud Messaging (FCM) is widely used for handling push notifications, which inherently involves background processing.
Step 1: Add Firebase Messaging Dependency
Add firebase_messaging
to your pubspec.yaml
:
dependencies:
firebase_messaging: ^14.0.0
Step 2: Configure Firebase Messaging
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
runApp(MyApp());
}
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
print("Handling a background message: ${message.messageId}");
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
FirebaseMessaging.instance.getInitialMessage().then((message) {
if (message != null) {
print('Initial message: ${message.notification?.body}');
}
});
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
print('Got a message whilst in the foreground!');
print('Message data: ${message.data}');
if (message.notification != null) {
print('Message also contained a notification: ${message.notification?.body}');
}
});
FirebaseMessaging.onMessageOpenedApp.listen((message) {
print('Message opened from background!');
print('Message data: ${message.data}');
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('FCM Example'),
),
body: Center(
child: Text('Firebase Cloud Messaging Example'),
),
),
);
}
}
Key aspects:
_firebaseMessagingBackgroundHandler
is a top-level function that handles messages when the app is in the background or terminated.FirebaseMessaging.onMessage
listens for messages when the app is in the foreground.FirebaseMessaging.onMessageOpenedApp
listens for when the user opens the app from a notification.
Conclusion
Implementing background tasks and services in Flutter requires careful consideration of platform limitations and available plugins. The compute
function is suitable for offloading CPU-intensive tasks, while plugins like flutter_background_service
, Workmanager
, and FCM provide robust solutions for periodic tasks, guaranteed background work, and push notifications, respectively. Properly managing these tasks can greatly enhance the user experience by ensuring timely updates and efficient resource utilization.