| title | Debugging Background Tasks |
|---|---|
| description | Debug and troubleshoot background tasks on Android and iOS |
Background tasks can be tricky to debug since they run when your app is closed. Here's how to effectively debug and troubleshoot them on both platforms.
The Workmanager plugin uses a hook-based debug system that allows you to customize how debug information is handled.
Initialize Workmanager without any debug parameters:
await Workmanager().initialize(callbackDispatcher);Then set up platform-specific debug handlers as needed.
Shows debug information in Android's Log system (visible in adb logcat):
// In your Application class
import dev.fluttercommunity.workmanager.WorkmanagerDebug
import dev.fluttercommunity.workmanager.LoggingDebugHandler
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
WorkmanagerDebug.setCurrent(LoggingDebugHandler())
}
}Shows debug information as notifications (requires notification permissions):
import dev.fluttercommunity.workmanager.NotificationDebugHandler
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
WorkmanagerDebug.setCurrent(NotificationDebugHandler())
}
}Shows debug information in iOS's unified logging system:
// In your AppDelegate.swift
import workmanager_apple
@main
class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
WorkmanagerDebug.setCurrent(LoggingDebugHandler())
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
}
}Shows debug information as notifications (helpful for seeing background task execution):
// In your AppDelegate.swift
import workmanager_apple
@main
class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Set notification delegate first
UNUserNotificationCenter.current().delegate = self
// Request notification permission
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
if granted {
print("Notification permission granted")
}
}
// Enable notification debug handler
WorkmanagerDebug.setCurrent(NotificationDebugHandler())
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
// REQUIRED: Override to show notifications when app is in foreground
override func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler(.alert) // Show notification banner even if app is in foreground
}
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
}
}Create your own debug handler for custom logging needs:
class CustomDebugHandler : WorkmanagerDebug() {
override fun onTaskStatusUpdate(context: Context, taskInfo: TaskDebugInfo, status: TaskStatus, result: TaskResult?) {
// Custom status handling logic
// See Task Status documentation for detailed status information
}
override fun onExceptionEncountered(context: Context, taskInfo: TaskDebugInfo?, exception: Throwable) {
// Handle exceptions
}
}
WorkmanagerDebug.setCurrent(CustomDebugHandler())class CustomDebugHandler: WorkmanagerDebug {
override func onTaskStatusUpdate(taskInfo: TaskDebugInfo, status: TaskStatus, result: TaskResult?) {
// Custom status handling logic
// See Task Status documentation for detailed status information
}
override func onExceptionEncountered(taskInfo: TaskDebugInfo?, exception: Error) {
// Handle exceptions
}
}
WorkmanagerDebug.setCurrent(CustomDebugHandler())For detailed information about task statuses, lifecycle, and notification formats, see the Task Status Tracking guide.
- Android Debugging — job scheduler inspection, adb commands, common Android issues.
- iOS Debugging — console logging, Xcode simulation, BGTaskScheduler, common iOS issues.
@pragma('vm:entry-point')
void callbackDispatcher() {
Workmanager().executeTask((task, inputData) async {
final startTime = DateTime.now();
print('🚀 Task started: $task');
print('📊 Input data: $inputData');
try {
// Your task implementation
final result = await performTask(task, inputData);
final duration = DateTime.now().difference(startTime);
print('✅ Task completed in ${duration.inSeconds}s');
return result;
} catch (e, stackTrace) {
final duration = DateTime.now().difference(startTime);
print('❌ Task failed after ${duration.inSeconds}s: $e');
print('📋 Stack trace: $stackTrace');
return false; // Retry
}
});
}Create a way to test your background logic from the UI:
// Add a debug button to test task logic
ElevatedButton(
onPressed: () async {
// Test the same logic that runs in background
final result = await performTask('test_task', {'debug': true});
print('Test result: $result');
},
child: Text('Test Task Logic'),
)Track when tasks last ran successfully:
Future<void> saveTaskExecutionTime(String taskName) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('${taskName}_last_run', DateTime.now().toIso8601String());
}
Future<bool> isTaskHealthy(String taskName, Duration maxAge) async {
final prefs = await SharedPreferences.getInstance();
final lastRunString = prefs.getString('${taskName}_last_run');
if (lastRunString == null) return false;
final lastRun = DateTime.parse(lastRunString);
final age = DateTime.now().difference(lastRun);
return age < maxAge;
}- Enable debug mode and install app
- Schedule task and verify it appears in job scheduler
- Put device to sleep and wait for execution
- Check debug notifications to confirm execution
- Use ADB commands to force execution if needed
- Test on physical device (simulator doesn't support background tasks)
- Enable Background App Refresh in iOS Settings
- Use Xcode debugger to trigger tasks immediately
- Monitor Xcode console for logging output
- Check iOS Settings > Battery for background activity
Quick Checks:
- [ ] Workmanager initialized in main()
- [ ] Task names are unique
- [ ] Platform setup completed (iOS setup guide)
- [ ] Debug handler configured (see Debug Handlers)
Performance & Reliability:
- [ ] Task logic optimized for background execution
- [ ] Dependencies initialized in background isolate
- [ ] Error handling with try-catch blocks
- [ ] iOS 30-second execution limit respected
Remember: Background task execution is controlled by the operating system and is never guaranteed. Always design your app to work gracefully when background tasks don't run as expected.