Handling Different States and Lifecycle Events of Background Tasks in Flutter

In Flutter development, managing background tasks effectively is crucial for creating robust and user-friendly applications. Background tasks allow you to perform operations like data synchronization, push notifications, or heavy computations without blocking the main UI thread. However, these tasks have different states and are influenced by lifecycle events. This blog post explores how to handle different states and lifecycle events of background tasks in Flutter.

Why Handle Background Task States and Lifecycle Events?

  • Resource Management: Properly managing task states helps in efficient resource utilization, preventing memory leaks or unnecessary CPU usage.
  • Data Consistency: Ensuring data consistency is vital when background tasks are involved in data modification.
  • User Experience: Informing users about the status of background tasks (e.g., through notifications) enhances the overall user experience.
  • Avoiding Errors: Lifecycle awareness helps in preventing common issues such as performing UI updates when the UI is no longer active.

Key Lifecycle Events to Consider

  • App Launch: Initializing or resuming tasks when the app starts.
  • App Resumed: Restarting or continuing tasks when the app comes back to the foreground.
  • App Paused: Pausing or stopping tasks when the app goes to the background.
  • App Suspended/Terminated: Cleaning up and persisting the state of tasks before the app is terminated.

Methods for Handling Background Tasks in Flutter

1. Using async and await

The simplest way to run code in the background is by using async and await with Dart’s Future. However, this approach is suitable for short, non-critical tasks that don’t need to survive app termination.


import 'dart:async';

void main() async {
  print('Starting background task...');
  await performBackgroundTask();
  print('Background task completed.');
}

Future performBackgroundTask() async {
  await Future.delayed(Duration(seconds: 5));
  print('Task executed in the background.');
}

This method is straightforward, but it’s not suitable for long-running tasks that need to survive the app’s lifecycle.

2. Flutter Background Services

For more complex scenarios, Flutter provides plugins and methods to run background services, such as using the flutter_background_service package.

Step 1: Add the flutter_background_service Dependency

dependencies:
  flutter_background_service: ^2.0.0
Step 2: Implement 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';

Future main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await initializeService();
  runApp(MyApp());
}

Future initializeService() async {
  final service = FlutterBackgroundService();

  await service.configure(
    androidConfiguration: AndroidConfiguration(
      // this will executed when app is in foreground inseparated isolate
      onStart: onStart,

      // auto start service
      autoStart: true,
      isForegroundMode: true,
    ),
    iosConfiguration: IosConfiguration(
      // auto start service
      autoStart: true,

      // this will executed when app is in foreground inseparated isolate
      onForeground: onStart,

      // you have to enable background fetch capability on xcode project
      onBackground: onIosBackground,
    ),
  );

  service.startService();
}

// to ensure this is executed
// run app from xcode, then from the xcode menu, select Debug > Attach to Process > Your App
// set chooseDifferentPackageName: true, set packageName: , then launch to debug the background isolate (https://github.com/fluttercommunity/flutter_foreground_task/issues/88)
@pragma('vm:entry-point')
Future onIosBackground(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();
  });

  // simple task
  Timer.periodic(const Duration(seconds: 1), (timer) async {
    if (service is AndroidServiceInstance) {
      if (await service.isForegroundService()) {
        service.setForegroundServiceFirstNotification(
          title: "My App Service",
          content: "Updated at ${DateTime.now()}",
        );
      }
    }

    print('Background service is running at ${DateTime.now()}');
  });
}

class MyApp extends StatefulWidget {
  @override
  State createState() => _MyAppState();
}

class _MyAppState extends State {
  String text = "Stop Service";

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Service App'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(
                'Service is running',
              ),
              ElevatedButton(
                onPressed: () {
                  final service = FlutterBackgroundService();
                  service.invoke("stopService");
                  Navigator.of(context).pop();
                },
                child: Text(
                  text,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Key points:

  • initializeService() initializes and configures the background service.
  • onStart() defines the task that the background service will perform.
  • FlutterBackgroundService() is used to start, stop, and configure the background service.

3. WorkManager

WorkManager is an API provided by Android Jetpack for managing background tasks. It is particularly useful for deferrable, guaranteed tasks that need to be executed even if the app is closed or the device restarts. For Flutter, you can use the workmanager package.

Step 1: Add the workmanager Dependency

dependencies:
  workmanager: ^0.5.0
Step 2: Implement the WorkManager

import 'dart:async';
import 'dart:isolate';

import 'package:flutter/material.dart';
import 'package:workmanager/workmanager.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 task is running. Useful for debugging tasks
  );
  Workmanager().registerPeriodicTask(
    "2",
    "simplePeriodicTask",
    frequency: Duration(minutes: 15),
  );
  runApp(MyApp());
}

void callbackDispatcher() {
  Workmanager().executeTask((task, inputData) {
    switch (task) {
      case Workmanager.BACKGROUND_TASK_DEFAULT_TAG:
        // ignore: avoid_print
        print(
            "Native adapter background task is executing.  task: $task inputData: $inputData");
        break;
      case "simpleTask":
        // ignore: avoid_print
        print("$task was executed. inputData = $inputData");
        break;
      case "simplePeriodicTask":
        // ignore: avoid_print
        print(
            "$task was executed. inputData = $inputData");
        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"),
        ),
        body: Center(
          child: Column(
            children: [
              ElevatedButton(
                  onPressed: () {
                    Workmanager().registerOneOffTask(
                      "task-identifier",
                      "simpleTask",
                      inputData: {'int': 1, 'bool': true, 'string': 'str'},
                    );
                  },
                  child: Text("Register OneOff Task")),
              ElevatedButton(
                  onPressed: () {
                    Workmanager().cancelByUniqueName("task-identifier");
                  },
                  child: Text("Cancel OneOff Task"))
            ],
          ),
        ),
      ),
    );
  }
}

Key Components:

  • Workmanager().initialize initializes the WorkManager with a callback dispatcher.
  • callbackDispatcher is a top-level function that handles different tasks.
  • Workmanager().registerOneOffTask registers a task to run once.
  • Workmanager().registerPeriodicTask registers a task to run periodically.

Handling Different States and Lifecycle Events

App Launch

When the app launches, you may need to resume any pending background tasks or initialize new ones.


void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await initializeBackgroundTasks();
  runApp(MyApp());
}

Future initializeBackgroundTasks() async {
  // Initialize or resume background tasks
}

App Resumed

When the app is resumed from the background, you may want to check the status of any ongoing tasks and update the UI accordingly.


import 'package:flutter/widgets.dart';
import 'package:flutter/scheduler.dart';

class _MyAppState extends State with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) {
      // Handle resumed state
      checkBackgroundTaskStatus();
    }
  }

  void checkBackgroundTaskStatus() {
    // Check and update UI
  }
}

App Paused/Suspended

When the app is paused or suspended, it’s important to pause or stop any non-critical tasks to conserve resources.


import 'package:flutter/widgets.dart';

class _MyAppState extends State with WidgetsBindingObserver {
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.paused) {
      // Pause or stop tasks
      pauseBackgroundTasks();
    }
    if (state == AppLifecycleState.detached) {
            _disconnect(); //Remove services when app is closing. 
        }
    if(state == AppLifecycleState.inactive){
           _disconnect(); //When route is remove
       }
  }

  void pauseBackgroundTasks() {
    // Pause background tasks logic
  }

    void _disconnect(){
        // logic when application goes out for all processings.
  }
}

App Terminated

Before the app is terminated, you should clean up resources and persist the state of any ongoing tasks.


void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // Persist task states
  await persistTaskStates();

  runApp(MyApp());
}

Future persistTaskStates() async {
  // Logic to save the state of tasks
}

Best Practices

  • Use Background Services for Long-Running Tasks: For tasks that need to run even when the app is in the background.
  • Utilize WorkManager for Deferrable Tasks: For tasks that must be completed reliably, even if the device restarts.
  • Handle App Lifecycle Events: Manage task states based on lifecycle events to optimize resource usage.
  • Inform the User: Provide feedback to the user about the status of background tasks to improve the user experience.
  • Test Thoroughly: Test background tasks in different scenarios to ensure they work as expected.

Conclusion

Handling different states and lifecycle events of background tasks in Flutter is essential for building efficient, reliable, and user-friendly applications. By using the appropriate tools and techniques, you can ensure that background tasks perform correctly under various conditions, enhancing the overall performance and user experience of your app. Understanding and implementing these practices is a key aspect of Flutter development.