Skip to content

Latest commit

 

History

History
283 lines (210 loc) · 8.59 KB

File metadata and controls

283 lines (210 loc) · 8.59 KB
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.

Hook-Based Debug System

The Workmanager plugin uses a hook-based debug system that allows you to customize how debug information is handled.

Quick Setup

Initialize Workmanager without any debug parameters:

await Workmanager().initialize(callbackDispatcher);

Then set up platform-specific debug handlers as needed.

Android Debug Handlers

Logging Debug Handler (Recommended)

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())
    }
}

Notification Debug Handler

Shows debug information as notifications (requires notification permissions):

import dev.fluttercommunity.workmanager.NotificationDebugHandler

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        WorkmanagerDebug.setCurrent(NotificationDebugHandler())
    }
}

iOS Debug Handlers

Logging Debug Handler (Recommended)

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)
    }
}

Notification Debug Handler

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)
    }
}
**Important:** The `userNotificationCenter` override is required to display debug notifications when your app is in the foreground. Without it, you'll only see notifications when the app is in the background.

Custom Debug Handlers

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.

Platform deep-dives

  • Android Debugging — job scheduler inspection, adb commands, common Android issues.
  • iOS Debugging — console logging, Xcode simulation, BGTaskScheduler, common iOS issues.

General Debugging Tips

Add Comprehensive Logging

@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
    }
  });
}

Test Task Logic Separately

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'),
)

Monitor Task Health

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;
}

Platform-Specific Testing

Android Testing Workflow

  1. Enable debug mode and install app
  2. Schedule task and verify it appears in job scheduler
  3. Put device to sleep and wait for execution
  4. Check debug notifications to confirm execution
  5. Use ADB commands to force execution if needed

iOS Testing Workflow

  1. Test on physical device (simulator doesn't support background tasks)
  2. Enable Background App Refresh in iOS Settings
  3. Use Xcode debugger to trigger tasks immediately
  4. Monitor Xcode console for logging output
  5. Check iOS Settings > Battery for background activity

Troubleshooting Checklist

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.