Implementing Background Processing with Workmanager in Flutter

Flutter is a popular framework for building cross-platform applications, known for its rapid development capabilities and beautiful UI. However, when it comes to background processing, handling tasks efficiently without draining the device’s resources is crucial. Workmanager, an Android Jetpack library, can be effectively used in Flutter to schedule and execute background tasks reliably, even when the app is closed or the device is idle. While Workmanager is primarily an Android solution, integrating it with Flutter apps can significantly enhance their functionality.

What is Workmanager?

Workmanager is an Android Jetpack library designed for enqueuing guaranteed, constraint-aware background tasks. It ensures that deferred work is executed, even if the application exits or the device restarts. Workmanager is suitable for tasks that are deferrable and require guaranteed execution, such as:

  • Syncing data with a remote server
  • Uploading logs
  • Applying filters to images

Why Use Workmanager in Flutter?

  • Reliability: Guaranteed execution even after app closure or device restart.
  • Constraint-Aware: Can be configured to run only when certain conditions are met (e.g., network connectivity, device idle).
  • Battery Efficiency: Optimized to minimize battery drain.
  • Backward Compatibility: Works with API 14 and above.

How to Integrate Workmanager with Flutter

To use Workmanager in a Flutter application, you’ll need to combine Flutter’s Dart code with native Android (Kotlin or Java) code. This involves creating a Flutter plugin that acts as a bridge between the two.

Step 1: Set Up Flutter Project

First, create a new Flutter project if you haven’t already:

flutter create background_processing_app

Step 2: Create a Flutter Plugin

Create a Flutter plugin to encapsulate the native Android code for Workmanager. Use the following command:

flutter create --org com.example background_processing
cd background_processing

This command sets up the basic structure for a Flutter plugin.

Step 3: Modify the Plugin’s pubspec.yaml

Update the pubspec.yaml file to include any necessary dependencies and details:

name: background_processing
description: A new Flutter plugin project.
version: 0.0.1
homepage:

environment:
  sdk: ">=2.12.0 <3.0.0"
  flutter: ">=1.20.0"

dependencies:
  flutter:
    sdk: flutter

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^1.0.0

flutter:
  plugin:
    platforms:
      android:
        package: com.example.background_processing
        pluginClass: BackgroundProcessingPlugin

Step 4: Implement Android (Kotlin) Code

Navigate to the android/src/main/kotlin/com/example/background_processing directory. You will find the BackgroundProcessingPlugin.kt file. Modify this file to integrate Workmanager.


package com.example.background_processing

import android.content.Context
import androidx.work.*
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import java.util.concurrent.TimeUnit

class BackgroundProcessingPlugin: FlutterPlugin, MethodCallHandler {
  private lateinit var channel: MethodChannel
  private lateinit var context: Context

  override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
    channel = MethodChannel(flutterPluginBinding.binaryMessenger, "background_processing")
    channel.setMethodCallHandler(this)
    context = flutterPluginBinding.applicationContext
  }

  override fun onMethodCall(call: MethodCall, result: Result) {
    when (call.method) {
      "startBackgroundWork" -> {
        startBackgroundWork()
        result.success("Background work started")
      }
      else -> {
        result.notImplemented()
      }
    }
  }

  override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
    channel.setMethodCallHandler(null)
  }

  private fun startBackgroundWork() {
    val constraints = Constraints.Builder()
      .setRequiredNetworkType(NetworkType.CONNECTED)
      .build()

    val workRequest = PeriodicWorkRequestBuilder(15, TimeUnit.MINUTES)
      .setConstraints(constraints)
      .build()

    WorkManager.getInstance(context).enqueueUniquePeriodicWork(
      "my_background_work",
      ExistingPeriodicWorkPolicy.KEEP,
      workRequest
    )
  }
}

class MyWorker(appContext: Context, workerParams: WorkerParameters): Worker(appContext, workerParams) {
  override fun doWork(): Result {
    // Perform background task here
    println("Background work is running...")
    return Result.success()
  }
}

In this Kotlin code:

  • The BackgroundProcessingPlugin class sets up the method channel and handles method calls from Flutter.
  • The startBackgroundWork() function defines the Workmanager request, specifying constraints such as network connectivity.
  • MyWorker is a worker class that extends Worker and overrides the doWork() method to perform the actual background task.

Step 5: Register the Worker in AndroidManifest.xml

Update the AndroidManifest.xml file (android/src/main/AndroidManifest.xml) to include the necessary permissions and to configure Workmanager:


    
    
    
    
    
    
        
        <!-- Other activities and configurations -->

        <!-- Add WorkManager configuration -->
        <provider
            android:name="androidx.work.impl.WorkManagerInitializer"
            android:authorities="${applicationId}.workmanager-init"
            android:exported="false"
            android:multiprocess="true" />

        <receiver
            android:name="androidx.work.impl.background.systemalarm.RescheduleReceiver"
            android:exported="false">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="android.intent.action.TIME_SET" />
                <action android:name="android.intent.action.TIMEZONE_CHANGED" />
            </intent-filter>
        </receiver>
    </application>

Make sure to add the necessary permissions like RECEIVE_BOOT_COMPLETED and INTERNET.

Step 6: Invoke the Method from Flutter

Now, in your main Flutter application (lib/main.dart), invoke the method defined in the plugin to start the background work:


import 'package:flutter/material.dart';
import 'dart:async';

import 'package:flutter/services.dart';

void main() {
  runApp(MyApp());
}

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

class _MyAppState extends State {
  String _message = 'Press the button to start background work';

  static const platform = const MethodChannel('background_processing');

  Future _startBackgroundWork() async {
    String message;
    try {
      final String result = await platform.invokeMethod('startBackgroundWork');
      message = result;
    } on PlatformException catch (e) {
      message = "Failed to invoke method: '${e.message}'.";
    }

    setState(() {
      _message = message;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Workmanager Example'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(_message),
              ElevatedButton(
                onPressed: _startBackgroundWork,
                child: Text('Start Background Work'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

In this Dart code:

  • The MethodChannel is used to communicate with the native Android code.
  • The _startBackgroundWork() function invokes the startBackgroundWork method defined in the Android plugin.
  • The result from the native method is displayed in the UI.

Step 7: Build and Run

Finally, build and run your Flutter application on an Android device or emulator:

flutter run

When you press the “Start Background Work” button, the background task defined in MyWorker will be scheduled and executed by Workmanager.

Conclusion

Integrating Workmanager with Flutter enables you to leverage Android’s robust background processing capabilities. By creating a Flutter plugin, you can seamlessly bridge the gap between Dart and native Android code. This approach ensures reliable, constraint-aware, and battery-efficient background task execution, enhancing the overall functionality and user experience of your Flutter applications.