flutter_foreground_task

repository·master·Indexed 41 days ago

https://github.com/dev-hwang/flutter_foreground_task

A Flutter plugin for implementing and managing foreground services on Android and background tasks on iOS. It enables repetitive tasks and two-way communication between a background service isolate and the main UI isolate. Version 11.0.3 requires Kotlin 2.2.20+ and Gradle 8.11.1+ for Android.

Tokens
14.5K
Snippets
52
Records
53
Agent score
51%

What's inside flutter_foreground_task

  1. Set up communication between TaskHandler and UI

    master

    The plugin supports two-way communication between the TaskHandler (background isolate) and the main UI isolate.

    1. Initialize the Communication Port

    Call FlutterForegroundTask.initCommunicationPort() in your main() function before runApp().

    2. Sending data from TaskHandler to UI

    In your TaskHandler, use FlutterForegroundTask.sendDataToMain(data). The data must be a primitive type (int, double, bool), a String, or a Collection (Map, List).

    3. Receiving data in the UI

    In your UI (e.g., a State class), use FlutterForegroundTask.addTaskDataCallback(callback) to listen for data. Always call FlutterForegroundTask.removeTaskDataCallback(callback) in dispose() to prevent memory leaks.

    4. Sending data from UI to TaskHandler

    In your UI, use FlutterForegroundTask.sendDataToTask(data). In the TaskHandler, handle this in the onReceiveData(Object data) override.

    // --- In main.dart ---
    void main() {
      FlutterForegroundTask.initCommunicationPort();
      runApp(const ExampleApp());
    }
    
    // --- In your UI State ---
    void _onReceiveTaskData(Object data) {
      if (data is Map<String, dynamic>) {
        print(data["timestampMillis"]);
      }
    }
    
    @override
    void initState() {
      super.initState();
      FlutterForegroundTask.addTaskDataCallback(_onReceiveTaskData);
    }
    
    @override
    void dispose() {
      FlutterForegroundTask.removeTaskDataCallback(_onReceiveTaskData);
      super.dispose();
    }
    
    // --- In your TaskHandler ---
    @override
    void onRepeatEvent(DateTime timestamp) {
      FlutterForegroundTask.sendDataToMain({"timestampMillis": timestamp.millisecondsSinceEpoch});
    }
    
    @override
    void onReceiveData(Object data) {
      print('Received from UI: $data');
    }
  2. Setup iOS AppDelegate for Swift

    master

    In a Swift-based iOS project, you must import the plugin and set the plugin registrant callback in ios/Runner/AppDelegate.swift to allow the background isolate to register plugins.

    import UIKit
    import Flutter
    import flutter_foreground_task // this (module import)
    
    @main
    @objc class AppDelegate: FlutterAppDelegate {
      override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
      ) -> Bool {
        GeneratedPluginRegistrant.register(with: self)
    
        // this
        SwiftFlutterForegroundTaskPlugin.setPluginRegistrantCallback { registry in
          GeneratedPluginRegistrant.register(with: registry)
        }
        if #available(iOS 10.0, *) {
          UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
        }
    
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
      }
    }
  3. Migrate to ver 8.0.0

    master

    Version 8.0.0 introduced significant changes to communication between the TaskHandler and the UI. The sendPort parameter was removed from callbacks, and the communication port must now be explicitly initialized.

    ### Communication Changes
    
    #### Sending data to Main Isolate
    ```dart
    // before
    void onStart(DateTime timestamp, SendPort? sendPort) {
      sendPort?.send(Object);
    }
    
    // after
    void onStart(DateTime timestamp) {
      FlutterForegroundTask.sendDataToMain(Object);
    }

    Initializing and Listening to Task Data

    // before
    final ReceivePort? receivePort = FlutterForegroundTask.receivePort;
    receivePort?.listen(_onReceiveTaskData);
    
    // after
    void main() {
      FlutterForegroundTask.initCommunicationPort();
      runApp(const ExampleApp());
    }
    
    FlutterForegroundTask.addTaskDataCallback(_onReceiveTaskData);
    FlutterForegroundTask.removeTaskDataCallback(_onReceiveTaskData);

    Renamed Method

    sendData is now sendDataToTask.

  4. Migrate to ver 8.6.0

    master

    Version 8.6.0 replaces interval and isOnceEvent in ForegroundTaskOptions with a single eventAction property using the ForegroundTaskEventAction constructor.

    FlutterForegroundTask.init(
      foregroundTaskOptions: ForegroundTaskOptions(
        // Use one of these:
        // ForegroundTaskEventAction.nothing() : No callback
        // ForegroundTaskEventAction.once() : Call once
        // ForegroundTaskEventAction.repeat(interval) : Call at interval
        eventAction: ForegroundTaskEventAction.repeat(5000),
      ),
    );
  5. Configure iOS background tasks

    master

    To enable background task execution on iOS, update your ios/Runner/info.plist with the permitted identifier and background modes.

    <key>BGTaskSchedulerPermittedIdentifiers</key>
    <array>
        <string>com.pravera.flutter_foreground_task.refresh</string>
    </array>
    <key>UIBackgroundModes</key>
    <array>
        <string>fetch</string>
    </array>
  6. Customize the iOS launch screen assets

    master

    To change the image displayed during the app's launch on iOS, you can either replace the image files directly in the file system or use Xcode.

    Option 1: File System Replace the existing image files located in the example/ios/Runner/Assets.xcassets/LaunchImage.imageset/ directory with your own assets.

    Option 2: Xcode (Recommended for visual management)

    1. Open the iOS workspace in Xcode by running open ios/Runner.xcworkspace from your terminal.
    2. In the Xcode Project Navigator, navigate to Runner/Assets.xcassets.
    3. Locate the LaunchImage asset set and drag and drop your desired images into it.
    open ios/Runner.xcworkspace
  7. Migrate to ver 7.0.0

    master

    In version 7.0.0, iconData and buttons were removed from AndroidNotificationOptions. Instead, use notificationIcon and notificationButtons directly within the startService method.

    // after
    await FlutterForegroundTask.startService(
      notificationTitle: 'Foreground Service is running',
      notificationText: 'Tap to return to the app',
      notificationIcon: null,
      notificationButtons: [
        const NotificationButton(id: 'btn_hello', text: 'hello'),
      ],
      callback: startCallback,
    );
  8. Migrate to ver 8.10.0

    master

    In version 8.10.0, the onStart and onDestroy callback return types changed from void to Future<void>.

    // before
    @override
    void onStart(DateTime timestamp, TaskStarter starter) { }
    
    @override
    void onDestroy(DateTime timestamp) { }
    
    // after
    @override
    Future<void> onStart(DateTime timestamp, TaskStarter starter) async { }
    
    @override
    Future<void> onDestroy(DateTime timestamp) async { }
  9. Setup iOS AppDelegate for Objective-C

    master

    If your iOS project uses Objective-C, you must add a bridging header and configure the AppDelegate.m to register the plugin callback.

    #import "AppDelegate.h"
    #import "GeneratedPluginRegistrant.h"
    @import flutter_foreground_task; // this (module import)
    
    // this
    void registerPlugins(NSObject<FlutterPluginRegistry>* registry) {
      [GeneratedPluginRegistrant registerWithRegistry:registry];
    }
    
    @implementation AppDelegate
    
    - (BOOL)application:(UIApplication *)application
        didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
      [GeneratedPluginRegistrant registerWithRegistry:self];
    
      // this
      [FlutterForegroundTaskPlugin setPluginRegistrantCallback:registerPlugins];
      if (@available(iOS 10.0, *)) {
        [UNUserNotificationCenter currentNotificationCenter].delegate = (id<UNUserNotificationCenterDelegate>) self;
      }
    
      return [super application:application didFinishLaunchingWithOptions:launchOptions];
    }
    
    @end
  10. Customize the Android notification icon

    master

    To change the icon displayed in the Android notification, you must provide a drawable resource and register it in your Android manifest via meta-data. This allows the flutter_foreground_task plugin to reference your custom icon.

    1. Prepare the icon

    1. Generate a notification icon using Android Asset Studio or the image generator in Android Studio.
    2. Move the generated icon image file to the android/app/src/res/drawable folder.

    2. Register the icon in AndroidManifest.xml

    Add a <meta-data> tag inside the <application> block. The android:name should be a unique identifier, ideally combining your package name with a descriptive suffix (e.g., com.your_package.service.HEART_ICON). The android:resource must point to your drawable file.

    Note: Do not change the android:name of the ForegroundService itself.

    <application>
        <!-- Warning: Do not change service name. -->
        <service
            android:name="com.pravera.flutter_foreground_task.service.ForegroundService"
            android:foregroundServiceType="dataSync"
            android:exported="false" />
    
        <!-- Register your custom icon here -->
        <meta-data
            android:name="com.your_package.service.HEART_ICON"
            android:resource="@drawable/ic_heart" />
    </application>
  11. Migrate to ver 11.0.0

    master

    Upgrading to version 11.0.0 requires updating the minimum SDK versions, migrating Android to built-in Kotlin (KGP), and updating iOS to use Swift Package Manager (SPM).

    ### SDK Requirements
    ```yaml
    sdk: ^3.12.0
    flutter: ">=3.44.0"

    Android: Migrate to built-in Kotlin (KGP)

    In android/app/build.gradle, replace the old kotlin-android plugin and kotlinOptions with the new kotlin block:

    plugins {
        // id "kotlin-android"
    }
    
    android {
        // kotlinOptions {
        //     jvmTarget = JavaVersion.VERSION_11
        // }
    }
    
    kotlin {
        compilerOptions {
            jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11
        }
    }

    Ensure your android/settings.gradle and gradle-wrapper.properties are updated to compatible versions (e.g., Gradle 8.14+ and Kotlin 2.2.20+).

    iOS: Support Swift Package Manager (SPM)

    Remove the Obj-C bridging header import from ios/Runner/Runner-Bridging-Header.h and use a module import in ios/Runner/AppDelegate.swift instead:

    import flutter_foreground_task // module import
  12. Configure Android permissions and service

    master

    To use the foreground service on Android, you must declare the service and necessary permissions in your AndroidManifest.xml.

    Requirements:

    • Kotlin version 2.2.20+
    • Gradle version 8.11.1+

    Steps:

    1. Add the required permissions inside the <manifest> tag.
    2. Declare the ForegroundService inside the <application> tag.
    3. Important: Do not change the service name.
    4. For Android 14+, you must specify the android:foregroundServiceType. If you want the service to stop when the app is closed, add android:stopWithTask="true" to the service tag.

    Android 15 Note: The dataSync service type has a 6-hour limit in a 24-hour period for apps targeting API level 35 or higher.

    <!-- required -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    
    <!-- Example for dataSync and remoteMessaging types -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING" />
    
    <!-- Warning: Do not change service name. -->
    <service 
        android:name="com.pravera.flutter_foreground_task.service.ForegroundService"
        android:foregroundServiceType="dataSync|remoteMessaging"
        android:exported="false" />