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
BackgroundProcessingPluginclass sets up the method channel and handles method calls from Flutter. - The
startBackgroundWork()function defines the Workmanager request, specifying constraints such as network connectivity. MyWorkeris a worker class that extendsWorkerand overrides thedoWork()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
MethodChannelis used to communicate with the native Android code. - The
_startBackgroundWork()function invokes thestartBackgroundWorkmethod 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.