Task Status Tracking

Understanding background task lifecycle and status notifications

Workmanager provides detailed task status tracking through debug handlers, allowing you to monitor the complete lifecycle of your background tasks from scheduling to completion.

Task Status Overview

Background tasks go through several status states during their lifecycle. The Workmanager plugin tracks these states and provides notifications through debug handlers.

Task Status States

StatusDescriptionWhen it occursAndroidiOS
ScheduledTask has been scheduled with the systemWhen registerOneOffTask() or registerPeriodicTask() is called
StartedTask execution has begun (first attempt)When task starts running for the first time
RetryingTask is being retried after a previous attemptWhen task starts running after runAttemptCount > 0
RescheduledTask will be retried laterWhen Dart function returns false
CompletedTask finished successfullyWhen Dart function returns true
FailedTask failed permanentlyWhen Dart function throws an exception
CancelledTask was cancelled before completionWhen cancelAll() or cancelByUniqueName() is called

Task Result Behavior

The behavior of task status depends on what your Dart background function returns:

Dart Function Return Values

Dart ReturnTask StatusSystem BehaviorDebug Notification
trueCompletedTask succeeds, won't retry Success
falseRescheduledWorkManager schedules retry with backoff🔄 Rescheduled
Future.error()FailedTask fails permanently, no retry Failed + error
Exception thrownFailedTask fails permanently, no retry Failed + error

Advanced Android Features

Retry Detection

On Android, Workmanager can distinguish between first attempts and retries using runAttemptCount:

ScenarioStart StatusEnd StatusNotification Example
Fresh task, succeedsStartedCompleted▶️ Started → Success
Fresh task, returns falseStartedRescheduled▶️ Started → 🔄 Rescheduled
Retry attempt, succeedsRetryingCompleted🔄 Retrying → Success
Retry attempt, failsRetryingFailed🔄 Retrying → Failed

Backoff Policy

When a task returns false, Android WorkManager uses exponential backoff by default:

  • 1st retry: ~30 seconds
  • 2nd retry: ~1 minute
  • 3rd retry: ~2 minutes
  • Maximum: ~5 hours

Debug Handler Integration

Task status is exposed through debug handlers. Set up debug handlers to receive status notifications:

Notification Configuration

The NotificationDebugHandler supports custom notification channels and grouping:

Android Options:

  • channelId: Custom notification channel ID for organizing notifications (if custom, you must create the channel first)
  • channelName: Human-readable channel name shown in system settings (only used if using default channel)
  • groupKey: Groups related notifications together in the notification drawer

iOS Options:

  • categoryIdentifier: Custom notification category for specialized handling
  • threadIdentifier: Groups notifications in the same conversation thread
// NotificationDebugHandler - shows status as notifications
WorkmanagerDebug.setCurrent(NotificationDebugHandler())

// Custom notification channel and grouping (you must create the channel first)
val channelId = "MyAppDebugChannel"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    val channel = NotificationChannel(channelId, "My App Debug", NotificationManager.IMPORTANCE_DEFAULT)
    val notificationManager = getSystemService(NotificationManager::class.java)
    notificationManager.createNotificationChannel(channel)
}
WorkmanagerDebug.setCurrent(NotificationDebugHandler(
    channelId = channelId,
    groupKey = "workmanager_debug_group"
))

// LoggingDebugHandler - writes to system log
WorkmanagerDebug.setCurrent(LoggingDebugHandler())

// Custom handler
class CustomDebugHandler : WorkmanagerDebug() {
    override fun onTaskStatusUpdate(
        context: Context, 
        taskInfo: TaskDebugInfo, 
        status: TaskStatus, 
        result: TaskResult?
    ) {
        when (status) {
            TaskStatus.SCHEDULED -> log("Task scheduled: ${taskInfo.taskName}")
            TaskStatus.STARTED -> log("Task started: ${taskInfo.taskName}")
            TaskStatus.RETRYING -> log("Task retrying (attempt ${taskInfo.runAttemptCount}): ${taskInfo.taskName}")
            TaskStatus.RESCHEDULED -> log("Task rescheduled: ${taskInfo.taskName}")
            TaskStatus.COMPLETED -> log("Task completed: ${taskInfo.taskName}")
            TaskStatus.FAILED -> log("Task failed: ${taskInfo.taskName}, error: ${result?.error}")
            TaskStatus.CANCELLED -> log("Task cancelled: ${taskInfo.taskName}")
        }
    }
}

Notification Format

The built-in NotificationDebugHandler shows concise, actionable notifications:

Notification Examples

StatusTitle FormatBody
Scheduled📅 ScheduledtaskName
Started▶️ StartedtaskName
Retrying🔄 RetryingtaskName
Rescheduled🔄 RescheduledtaskName
Success SuccesstaskName
Failed FailedtaskName + error message
Exception ExceptiontaskName + exception details
Cancelled CancelledtaskName

Platform Differences

Android Advantages

  • Retry detection: Can distinguish first attempts from retries
  • Automatic rescheduling: WorkManager handles retry logic with backoff
  • Rich debug info: Access to runAttemptCount and system constraints
  • Guaranteed execution: Tasks will retry according to policy

iOS Limitations

  • No retry detection: Cannot distinguish first attempts from retries
  • Manual rescheduling: App must reschedule tasks on failure
  • System controlled: iOS decides when/if tasks actually run
  • No guarantees: Tasks may never execute depending on system state

Best Practices

Task Implementation

@pragma('vm:entry-point')
void callbackDispatcher() {
  Workmanager().executeTask((task, inputData) async {
    try {
      // Your task logic here
      final result = await performWork(task, inputData);
      
      if (result.isSuccess) {
        return true;  // ✅ Task succeeded
      } else {
        return false; // 🔄 Retry with backoff (Android) or manual reschedule (iOS)
      }
    } catch (e) {
      // 🔥 Permanent failure - will not retry
      throw Exception('Task failed: $e');
    }
  });
}

Error Handling Strategy

Error TypeRecommended ReturnResult
Network timeoutreturn falseTask will retry later
Invalid datathrow Exception()Task fails permanently
Temporary server errorreturn falseTask will retry with backoff
Authentication failurethrow Exception()Task fails, needs user intervention

Monitoring Task Health

// Track task execution in your debug handler
class TaskHealthMonitor : WorkmanagerDebug() {
    override fun onTaskStatusUpdate(context: Context, taskInfo: TaskDebugInfo, status: TaskStatus, result: TaskResult?) {
        when (status) {
            TaskStatus.COMPLETED -> recordSuccess(taskInfo.taskName)
            TaskStatus.FAILED -> recordFailure(taskInfo.taskName, result?.error)
            TaskStatus.RETRYING -> recordRetry(taskInfo.taskName)
        }
    }
}

Troubleshooting

Common Issues

Tasks showing as "Rescheduled" but not running:

  • Android: Check battery optimization and Doze mode settings
  • iOS: Verify Background App Refresh is enabled and app is used regularly

Tasks immediately failing:

  • Check if task logic throws exceptions during initialization
  • Verify all dependencies are available in background isolate
  • Review error messages in Failed notifications

No status notifications appearing:

  • Ensure debug handler is set before task execution
  • Check notification permissions (for NotificationDebugHandler)
  • Verify debug handler is called during task lifecycle

For detailed debugging guidance, see the Debugging Guide.

Migration from isInDebugMode

If you were using the deprecated isInDebugMode parameter:

// ❌ Old approach (deprecated)
await Workmanager().initialize(
  callbackDispatcher,
  isInDebugMode: true, // Deprecated
);

// ✅ New approach
await Workmanager().initialize(callbackDispatcher);

// Set up platform-specific debug handler
// Android: WorkmanagerDebug.setCurrent(NotificationDebugHandler())
// iOS: WorkmanagerDebug.setCurrent(LoggingDebugHandler())

The new system provides much more detailed and customizable debugging information than the simple boolean flag.