Inspect scheduled work¶
val tasks: List<ScheduledTask> = backgrounder.scheduled()
tasks.forEach { task ->
println("${task.taskId} → ${task.kind} ${task.state} attempt=${task.attempt}")
task.pendingPredicates.forEach { predicate -> println(" waiting on: $predicate") }
}
scheduled() returns a snapshot — a list of every task the library knows about. It's suspend because Android's WorkManager.getWorkInfos returns a ListenableFuture and iOS's BGTaskScheduler.getPendingTaskRequests is callback-shaped; the suspend signature lets us await both cleanly.
Fields on ScheduledTask¶
| Field | Meaning |
|---|---|
taskId |
The stable TaskId you scheduled. |
kind |
OneTime or Periodic. |
state |
One of Pending, Running, Backoff, Blocked. Best-effort per platform — see below. |
nextRunHint |
Best-effort Instant of the next scheduled run. May be null. |
attempt |
Library-tracked retry attempt counter (within a cycle for periodic). |
ephemeral |
True if this task was scheduled with ephemeral = true. |
pendingPredicates |
Why this task isn't running yet — see Why work is pending. |
Per-platform state mapping¶
- Android.
stateis derived directly fromWorkInfo—RUNNING→Running,ENQUEUED→Pending(orBackoffifrunAttemptCount > 0),BLOCKED→Blocked. - iOS. The library does not directly observe a worker between handler-fire and
setTaskCompletedSuccess, so that span is reported asPendingrather thanRunning.Backoffmeans the library's state store says active + iOS has no pending request + attempt counter is non-zero.Blockedis rare (typically force-quit aftermath before the resurrection sweep). - macOS.
Backoffif the library's last result wasRetry; otherwisePending.
Reactive event stream¶
The library exposes a hot SharedFlow<MonitorEvent> for every scheduling, dispatch, deferral, completion, retry, cancellation, and library-internal error — see the monitor recipe for the full event vocabulary. Inspector UIs typically combine a snapshot poll (the scheduled() example above) with the event stream:
backgrounder.events()
.filterIsInstance<MonitorEvent.WorkCompleted>()
.collect { e -> println("${e.taskId} finished in ${e.runtime}: ${e.result}") }
MonitorEvent is a sealed interface — Swift consumers get an exhaustive enum via onEnum(of:), and the same on Kotlin via when.
Registered factories and platform diagnostics¶
// Which factories own which task ids (for inspector attribution).
backgrounder.registeredFactories().forEach { d ->
println("${d.factoryId ?: "<anonymous>"} → ${d.taskIds}")
}
// Is the environment configured correctly?
val diag = backgrounder.diagnostics()
if (!diag.isHealthy) {
diag.diagnostics.forEach { println("⚠ $it") }
}
diagnostics() reports cross-platform issues like RegistryNotSealed, iOS-only ones like MissingInfoPlistEntry(taskId), and Android-only ones like WorkManagerNotInitialized. Run it at app launch as a health check.