Running Background Tasks and Services in Flutter

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 named simpleTask 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.