Releases: brewkits/native_workmanager
Release list
v1.4.3 — iOS SwiftPM: manifest fix for stricter toolchains
Fixed
- iOS SwiftPM: manifest rejected by stricter SwiftPM toolchains.
Package.swiftdeclared a test target withpath: "../Tests", which escapes the package root. Newer SwiftPM (Xcode 26.x) tolerates it, but stricter toolchains reject the entire manifest at load time ("target 'NativeWorkManagerTests' in package 'native_workmanager' is outside the package root") — breaking dependency resolution for every SPM-enabled consumer on those toolchains, the same failure mode as #49/#52. The test target is removed from the consumer-facing manifest; nothing ever executed it, so no coverage is lost. Root cause confirmed by controlled experiment: the CI job reproducing the failure goes green with the target removed.
With this, all four layers of the SwiftPM install path are verified automatically on every PR: remote binary target resolution, hyphenated product-name resolution, a full flutter build of a consuming app with SwiftPM enabled, and compile under SPM's strict module isolation.
CI
Validate PRjob: PR title/body now flow throughenvinstead of being inlined into bash — fixes script breakage on special characters and closes a script-injection vector for fork PRs.
No Android or Dart-API changes. The bundled KMPWorkManager.xcframework is unchanged (same v1.4.0 release asset and checksum).
v1.4.2 — iOS SwiftPM: hyphenated library product (issue #52)
Fixed
- iOS SwiftPM resolution failed one layer above the 1.4.1 fix — Issue #52. Flutter's generated
FlutterGeneratedPluginSwiftPackagereferences every plugin by the hyphenated library product name (SwiftPM uses the product name asCFBundleIdentifierwhen linking dynamically, and bundle identifiers cannot contain underscores).Package.swiftexported the product asnative_workmanager, so SPM-enabled apps failed dependency resolution with "product 'native-workmanager' … not found in package 'native_workmanager'". The library product is nownative-workmanager; package and target names keep underscores. Thanks @zaqwery for the precise diagnosis — again. - iOS: two workers did not compile under SwiftPM.
CryptoWorkerandFileSystemWorkeruseUIApplicationwithout an explicitimport UIKit; CocoaPods builds compiled anyway via transitive module re-export, SwiftPM builds do not. Explicit imports added.
Changed
- Release verification now includes a real SwiftPM app build (scratch Flutter app with
--enable-swift-package-managerdepending on the plugin), exercising Flutter's generated manifest end-to-end. Verified through to a runtime smoke test: nativeFileSystemWorkerexecutes under an SPM-built app.
No Android or Dart-API changes. The bundled KMPWorkManager.xcframework is unchanged — SwiftPM/CocoaPods installs keep fetching the existing v1.4.0 release asset (same checksum).
v1.4.1 — SwiftPM fix (#49) + Android 15/16 DNS + cancellation handling
Fixed
-
iOS Swift Package Manager builds failed for pub.dev consumers — Issue #49.
Package.swiftdeclaredKMPWorkManageras a local.binaryTarget
(path: "../Frameworks/KMPWorkManager.xcframework"), but that xcframework is
stripped from the published package by.pubignoreand only re-created by the
CocoaPodsprepare_commandat install time. SwiftPM has no equivalent install
hook, so with Flutter's SwiftPM integration enabled the local binary target
resolved to nothing andxcodebuildaborted with "local binary target
'KMPWorkManager' … does not contain a binary artifact" — and because Flutter
routes a plugin through SwiftPM whenever aPackage.swiftexists (excluding it
from CocoaPods), there was no fallback. Replaced the local target with a
remote, checksummed.binaryTargetpointing at the same GitHub-release zip
the podspec already downloads, so SwiftPM fetches the identical versioned
artifact CocoaPods does. Verified end-to-end: SwiftPM downloads the asset,
validates the checksum, and builds. Thanks @zaqwery for the precise root-cause
report. -
CancellationExceptionswallowed by generic exception handling in 11 Android
workers. A worker cancelled mid-run (user callscancel()/cancelAll(), or
WorkManager stops the worker because constraints are no longer met) could have
itsCancellationExceptioncaught by the worker's owncatch (e: Exception)
and converted into a normalWorkerResult.Failure— inHttpDownloadWorker's
case withshouldRetry: true, meaning a task the user explicitly cancelled
could reschedule itself.ForegroundNativeWorkerwas the most exposed case: it
bypassesBaseKmpWorker, so nothing else catches cancellation correctly for
the FGS-bypass path. Fixed by addingcatch (e: CancellationException) { throw e }before the generic catch in every worker where the wrapped scope contains
a real suspension point (network I/O awaited via child coroutines,delay(),
setForeground()). Affected:DbCleanupWorker,FileCompressionWorker,
FileDecompressionWorker,FileSystemWorker,ForegroundNativeWorker(two
sites),HttpDownloadWorker,HttpRequestWorker,HttpSyncWorker,
HttpUploadWorker,ImageProcessWorker,ParallelHttpDownloadWorker(two
sites). Five workers audited and confirmed already safe without changes
(CryptoWorker,MoveToSharedStorageWorker,ParallelHttpUploadWorker,
PdfWorkerrely onBaseKmpWorker's outerCancellationExceptionhandling
since they have no local catch around their dispatch;WebSocketWorker
already usedtry/finallyinstead oftry/catcharound its
cancellation-sensitive section). -
Intermittent "Failed host lookup" on Android 15/16. Bumped
androidx.work:work-runtime-ktx2.10.1 → 2.11.2, which fixes an upstream
AndroidX WorkManager bug where a backgroundWorkRequestcould start
running before the device's network/connectivity state was fully attached,
causing spuriousSocketException: Failed host lookupfailures on HTTP
calls made from background tasks. Found by auditingflutter_workmanager's
issue tracker (still pinned to 2.10.2 at the time of writing, with an open
unresolved report) — see issuetracker.google.com/issues/445324855.
kmpworkmanagerpulls inwork-runtime-ktx2.9.1 transitively; the direct
apideclaration here wins Gradle's highest-version resolution (verified:
./gradlew :native_workmanager:dependenciesresolves2.9.1 -> 2.11.2).
v1.4.0 — DartWorker retry-on-false + Constraints.maxRetries enforcement
Changed
- Bumped
kmpworkmanagercore to 3.1.0 (was 3.0.1). 3.1.0 enforcesConstraints.maxRetriesinsideBaseKmpWorker: it reads themaxRetrieskey off the WorkRequest input data and capsFailure(shouldRetry=true)/RetryatN + 1total runs (WorkManager itself has no max-retry API — a rawResult.retry()reschedules forever). The bundled iOSKMPWorkManager.xcframeworkwas rebuilt from 3.1.0 (attached below — the podspec downloads it for pub.dev installs).
Fixed
-
DartWorker
return falsenever retried — permanentResult.failure(). AndroidDartCallbackWorkerand iOS Dart callback paths mapped afalsecallback result toWorkerResult.Failure/.failurewithoutshouldRetry: true, soConstraints.maxRetries/backoffDelayMswere ignored despite docs promising retry-on-false. Native engine/setup exceptions still useshouldRetry = falseso broken engine configuration does not loop forever. Thanks @rajabilal555 for finding and fixing this (#46). -
Android
Constraints.maxRetrieswas silently ignored. Even once a task asked to retry, WorkManager'sResult.retry()is unbounded, so a callback that kept returningfalselooped forever.maxRetriesis now forwarded onto the KMPConstraintsand stamped onto WorkRequest input data for every direct-enqueue path (one-time, chain, graph). Periodic work is intentionally excluded — itsrunAttemptCountonly resets on success.ForegroundNativeWorkerenforces the same cap inline. iOSRetryConfignow readsmaxRetriesviaNSNumber(MethodChannel integers were silently dropped to0= no retry) and defaults to3to match the Dart contract.
Note: this is a behavior change — a DartWorker callback returning false (or throwing) now retries under maxRetries/backoff instead of failing permanently. Set Constraints(maxRetries: 0) for the old fail-fast behavior.
v1.3.3 — DartWorker progress (#38) & persisted status (#39) + HMAC hardening
Device-verified on a real Pixel 6 Pro and an iPhone 17 simulator. Both native_workmanager and native_workmanager_gen score 160/160 on pana.
Fixed
- DartWorker progress events dropped (UI stuck at 0%) — #38. Native emitted the progress map without a
timestamp, so the Dart session-filter silently discarded every event. AndroidProgressUpdate.toMap()/toJson()and iOSProgressReporter/emitProgressnow stamptimestamp; the Dart filter treats a missing/0timestamp as "current".- Two further iOS-only gaps surfaced by device testing (not covered by the original community fix):
__taskIdis now injected into a foreground DartWorker's input (executeDartWorkerViaMethodChannel, mirroring Android), and the main Flutter engine now registers thedev.brewkits/dart_worker_channelreportProgresshandler so foreground callbacks no longer throwMissingPluginException.reportDartWorkerProgressnow works from a foreground DartWorker on both platforms.
- Two further iOS-only gaps surfaced by device testing (not covered by the original community fix):
- DartWorker TaskStore status stuck on
pendingafter success — #39. TheobserveWorkCompletionWorkInfo fallback now persists terminal status to SQLite (running/completed/failed/cancelled), plus asyncTaskStoreWithWorkManager()reconciliation on restart repairs rows left stale by process death.allTasks()now reflects the real lifecycle.
Security
- Constant-time HMAC signature comparison in RemoteTrigger. Verification compared signatures with plain string equality (Android
String.equals, iOS==), which short-circuits and can leak — via response timing — how many leading bytes matched. Both platforms now compare the raw HMAC bytes in constant time (AndroidMessageDigest.isEqual, iOS CryptoKitHMAC.isValidAuthenticationCode). Behavior-preserving.
Docs
- README: replaced the outdated "your
Applicationmust implementConfiguration.Provider" note with the v1.3.0+ zero-config auto-init story; added areportDartWorkerProgressexample. Install snippets and API reference bumped to 1.3.3.
Companion package
native_workmanager_gensynced to 1.3.3 (no codegen changes).
Full changelog: https://github.com/brewkits/native_workmanager/blob/main/CHANGELOG.md
Thanks @rajabilal555 for the #38/#39 root-cause analysis and the original fix (#40).
v1.3.2 — Issue #36 iOS UIScene crash fix + pre-publish hardening
Fixed
iOS: startup crash on Flutter 3.38+ (UIScene template) — Issue #36
Apps created with the Flutter 3.38+ iOS template register plugins in AppDelegate.didInitializeImplicitFlutterEngine, which runs after application(_:didFinishLaunchingWithOptions:) returns. Calling BGTaskScheduler.register at that point violates Apple's "all launch handlers must be registered before application finishes launching" rule and threw NSInternalInconsistencyException at startup — reported on iPhone 15 / iOS 18.6.2, but affects any device on the new template.
- BGTask launch handlers now register in an ObjC
+loadhook (NWMBGTaskRegistrar) that runs at binary load time — always inside the launch window, on both the old and the new template. Plugin registration only attaches the Swift handlers afterwards. No app-side changes required. - All
BGTaskScheduler.registercalls now go through ObjC@try/@catch(Swift cannot catchNSException): late or duplicate registration degrades to aBGTASK_REGISTRATION_FAILEDsystem error instead of a crash. - Fixed a latent duplicate-registration crash:
registerHandlers()had no idempotency guard, soGeneratedPluginRegistrantre-running on the headless background engine (FlutterEngineManager) re-registered the identifiers and threw the same exception. - BGTasks that fire before the Swift side attaches (cold-start background launch) are now buffered and delivered once handlers attach.
- Fixed along the way: an ObjC bool-boxing bug (
@(a != nil)boxes asNSNumberint, which the Flutter standard codec decodes as Dartintrather thanbool) in the registrar's debug snapshot — caught by the newissue_36device integration test.
The example app has been migrated to the Flutter 3.38+ UIScene template (FlutterImplicitEngineDelegate + SceneDelegate) so the device integration suite runs on the exact lifecycle that crashed — that migration is the regression coverage for this fix.
Dependency: kmpworkmanager core 2.5.1 → 3.0.1
Fixes a critical crash on Android 8–11 (API 26–30): expedited tasks threw IllegalStateException: Not implemented due to a missing getForegroundInfo() override, regressed since core v2.3.8. (v3.0.0 also extracted Ktor HTTP workers into the optional kmpworkmanager-http artifact — not used by this plugin, no bridge API changes.)
Added
- iOS:
NativeWorkmanagerPlugin.registerBGTaskHandlers()— optional explicit registration fromdidFinishLaunchingWithOptions(idempotent, exception-safe). Only needed if a build setup strips ObjC+loadsections. native_workmanager_gen: first test suite (previously zero tests despite declaringtest:as a dependency) — 15 tests covering code generation, snake_case/kebab-case → camelCase ID conversion, typed-input enqueue wrapper generation, and all 7 validation-error paths.
Documentation
A full pre-publish audit found compile-breaking errors in every documentation file that referenced the public API. All fixed:
- Nonexistent worker methods documented:
imageResize/imageCrop/imageConvert/imageThumbnail(the real API is the unifiedimageProcess()),parallelDownload(real:parallelHttpDownload),imagesToPdf(real:pdfFromImages),cryptoHash/hmacSign(no such methods exist). - Wrong parameter names:
maxAttempts→maxRetries,deviceIdle→requiresDeviceIdle,requiresWifi→requiresUnmeteredNetwork,TaskEvent.outputData→resultData. - Fabricated APIs that don't exist anywhere in the package: a
retryPolicy/RetryPolicyenqueue parameter (retry config actually lives inConstraints(backoffPolicy:, backoffDelayMs:, maxRetries:)),NativeWorkManager.registerCallback()(callbacks register via thedartWorkersmap passed toinitialize()), aTaskStateenum withevent.state/.progress/.attemptCount/.error(realTaskEventfields aresuccess,isStarted,message,errorCode,resultData),ImageCropRect(real type isdart:ui'sRect), and anenqueueToQueue()method (offline queueing goes throughOfflineQueue/QueueEntry/OfflineRetryPolicy). - Every
@pragma('vm:entry-point')callback sample across the Dio, Firebase, Sentry, and Hive integration guides used the wrong signature (Future<void> fn(String? input)) instead of the realDartWorkerCallbacksignature (Future<bool> Function(Map<String, dynamic>? input)) — roughly 35 functions corrected. ForegroundServiceTypeenum reference listed 7 values that don't exist and omitted the 2 real ones.- Stale version pins (v1.0.x–v1.2.x) across README and all guides updated to v1.3.2.
Every fix was verified against the actual lib/src/*.dart source — not guesswork.
Cleanup
- Removed 4 leftover debug
print()calls fromTaskGraph's internal executor. - Removed 8 dead/orphaned scratch files from the example app (one-off issue-verification scripts, an unwired demo entry point, unused duplicate widgets) — none were referenced anywhere in the app.
- Fixed the demo app displaying a hardcoded "v1.2.7" in its UI (title bar, engine log, app bar) despite shipping v1.3.2.
Verification
flutter analyze: 0 issues (bothnative_workmanagerandnative_workmanager_gen)- Host test suite: 1883/1883 passing (unit, security, performance, integration, workers, widgets)
native_workmanager_gentest suite: 15/15 passing (new)- Device integration tests on real hardware across 8 groups — All Workers, Constraints, Task Chains, Events, Cancellation, Trigger Types, Custom Native Workers, and the new
issue_36group — all green on both platforms - Demo app built, installed, and driven manually on both platforms with log capture
Notes
native_workmanager_gencompanion package bumped to 1.3.2 (in sync).- No Flutter SDK version bump required — the fix is template-agnostic; the
pubspec.yamlconstraint stays>=3.27.0.
Full Changelog: v1.3.1...v1.3.2
v1.3.1
Fixed
Android (critical regression, since v1.2.4)
All file-based native workers (HttpDownload, HttpUpload, ParallelHttpDownload/Upload, FileCompression, FileDecompression, ImageProcess, Crypto hash/encrypt/decrypt, Pdf, WebSocket, FileSystem, MoveToSharedStorage) failed on real devices with Invalid or unsafe file path. v1.2.4 added a blanket "/data" entry to SecurityValidator's blocked-prefix list, which rejected the app's own private sandbox (/data/data/<pkg>, /data/user/<n>/<pkg> — exactly what path_provider returns). The validator now blocks only the genuinely OS-owned sub-directories of /data while allowing the app sandbox. Path-traversal protection and blocking of /proc, /sys, /etc, /system, /vendor, /dev, /root are unchanged.
Verified on Pixel 6 Pro (Android 16): the "All Workers" integration group passes 15/15.
iOS (#33)
KMPWorkManager.xcframework was extracted into a double-nested path (Frameworks/Frameworks/KMPWorkManager.xcframework) during pod install, causing iOS builds to fail with Unable to find module dependency: 'KMPWorkManager'. The prepare_command in native_workmanager.podspec is now layout-agnostic.
Notes
- The framework binary (kmpworkmanager 2.5.1) is unchanged; the podspec continues to download the xcframework from the v1.3.0 release asset.
native_workmanager_gencompanion package also bumped to 1.3.1.
Full Changelog: v1.3.0...v1.3.1
v1.3.0 - Zero-Config Auto-Init & Unified CLI
[1.3.0] - 2026-06-04
Added
-
Android Auto-Init (
NativeWorkManagerInitializer): Plugin now ships anandroidx.startup
Initializerdeclared in its ownAndroidManifest.xml. It runs automatically before
Application.onCreate(), restoring thecallbackHandlefrom SharedPreferences and
initializingKmpWorkManagerwithSimpleAndroidWorkerFactory.- Breaking zero-config change:
DartWorkerkilled-app support now requires no custom
Applicationclass and no manualAndroidManifest.xmledits for the common case. - Opt-out for apps with custom WorkManager configuration: add
<meta-data android:name="native_workmanager.auto_init" android:value="false" />to
<application>in yourAndroidManifest.xml, then followdoc/ANDROID_SETUP.md. isSchedulerInitializedflag prevents double-initialization whenonAttachedToEngine
runs after the Initializer.
- Breaking zero-config change:
-
Unified setup CLI (
dart run native_workmanager:setup): Evolvessetup_iosinto a
universal command covering both platforms.--android: validates the app manifest has no conflicts with auto-init.--ios: patchesInfo.plistwithUIBackgroundModesand
BGTaskSchedulerPermittedIdentifiers(same as the legacysetup_ioscommand).--check: read-only validation mode — no files are written.--help: full usage reference.setup_iosexecutable retained for backward compatibility.
-
iOS
WorkerResult.retry(): Addedretry(reason:delayMs:attemptCap:)factory on
the SwiftWorkerResultstruct, providing parity withWorkerResult.Retryintroduced
in kmpworkmanager v2.5.0.
Changed
-
Core: Upgraded KMP WorkManager core dependency from v2.4.3 to v2.5.1.
- Android: added
WorkerResult.Retrybranch inForegroundNativeWorkerto satisfy
sealed-class exhaustiveness (maps toResult.retry()). - iOS
KMPWorkManager.xcframeworkrebuilt from v2.5.1 source.
- Android: added
-
iOS retry semantics (
executeWorkerSync): the retry loop now respects
WorkerResult.shouldRetry. A worker returningfailure(shouldRetry: false)stops
retrying immediately instead of exhausting allmaxRetriesattempts. -
iOS
maxRetrieshonored on the direct-task execution path:RetryConfig.from(constraintsMap:)
is now called and passed toexecuteWorkerSync. PreviouslyConstraints.maxRetrieswas
silently ignored on iOS (dead code). -
iOS direct-task
qosnow read fromconstraintsMap["qos"]instead of being
hardcoded to"background".
Fixed
-
Android
DartCallbackWorker:CancellationExceptionis now rethrown before the
outercatch (Exception)block.executeDartCallbackis a suspending function; without
this fix, WorkManager task cancellation was silently converted to aFailureresult. -
iOS WebSocket:
NativeWorker.webSocket()now throwsUnsupportedErrorat call-site
when run on iOS. Previously the task was enqueued and silently failed with
"Unknown worker class" becauseIosWorkerFactoryhas noWebSocketWorkercase. -
Android
handleResume: constraint JSON parse failure now logs aNativeLogger.w
warning instead of silently falling back to empty constraints (which could cause resumed
downloads to ignorerequiresNetwork/requiresCharging). -
Dart
resolveDispatcherTimeout: values ≤ 0 (zero, negative, NaN, ±Infinity) now
fall back to the 25 s default. ADuration(milliseconds: -n).timeout()fires immediately,
which would kill every DartWorker. Added four regression tests. -
Android
HttpDownloadWorker— data corruption (directory mode): concurrent downloads
to the same directory now each use their own temp file (__pending_<taskId>__.tmp)
instead of sharing the hardcoded__pending__.tmp. Two workers writing to the same
temp path produced a mixed-byte file; the first to finish would rename corrupted data. -
Android
HttpDownloadWorker— TOCTOU rename (onDuplicate: "rename"): replaced
findNextAvailableFile() + Files.move(REPLACE_EXISTING)with an atomic probe loop using
ATOMIC_MOVEonly (noREPLACE_EXISTING). AFileAlreadyExistsExceptionnow signals
the next candidate rather than silently overwriting a file from a concurrent download. -
Android constraint conflict warning: enqueueing with
allowWhileIdle: trueand
isHeavyTask: truesimultaneously now logs aNativeLogger.wat enqueue time. The
long-running worker already bypasses Doze mode, makingallowWhileIdleredundant and
potentially causing WorkManager rejection on some Android versions.
v1.2.7 - Enforce DartWorker timeoutMs end-to-end (Issue #30)
Fixed
- Core: Enforced
DartWorker.timeoutMsend-to-end (Issue #30).- Android and iOS bridges now correctly forward
timeoutMsto the Dart callback dispatcher. - Added
resolveDispatcherTimeouthelper in Dart to securely parse the timeout, protecting againstNaN,Infinity, and invalid types. - Enforced
timeoutMsin both the background dispatcher and the foregroundMethodChannel(_executeDartCallback). - Added comprehensive unit, integration, performance, and security test coverage.
- Android and iOS bridges now correctly forward
v1.2.6: Industrial Stability & Foreground Service (FGS) Bypass
🚀 Version 1.2.6: Industrial-Grade Background Reliability
This release marks a major milestone in production stability, introducing industrial-grade mechanisms to bypass modern OS background restrictions and eliminating critical concurrency issues.
🌟 Key Highlights
🛡️ Android: Industrial Foreground Service (FGS) Bypass
Run heavy, long-running tasks without being killed by Android 12+ battery optimizations.
- Priority Execution: Promotes tasks to system-level Foreground Services.
- Custom Notifications: Full control over title, body, and action buttons (e.g., 'Stop Sync').
- Android 14 Ready: Automatically handles FGS types and required system permissions.
⚡ Android: Expedited Work (Locked Device Support)
Resolved the regression where tasks wouldn't fire when the screen was locked.
- Doze Mode Bypassing: Maps
allowWhileIdle: trueto WorkManager's Expedited Work. - Instant Triggering: Tasks fire reliably even on idle or locked devices.
💎 iOS: Swift Concurrency & Deadlock Fixes
- Serial Database Queues: Migrated all SQLite stores to serial queues to eliminate deadlocks.
- Scheduling Reliability: Integrated micro-delays for complex
TaskGraphchains to ensureBGTaskSchedulerregistration success.
🛠 Full Change Log
Added
- Android: Industrial-grade Foreground Service support via
ForegroundNotificationConfig. - Android: Proactive task promotion using
setForeground()for immediate background execution. - Android: Full compliance with Android 14 (API 34) Foreground Service Types.
- Example: New 'FGS Bypass' demo page showing real-time priority task execution.
Fixed
- Android: Fixed regression where background tasks would not fire on locked devices (#28).
- iOS: Fixed Swift Concurrency deadlocks affecting
OfflineQueueandTaskGraph. - iOS: Synchronized background task identifiers between Swift code and setup scripts.
- Test: Platform-aware integration test suite with automatic iOS Simulator isolation.
Documentation
- Architecture: Deep-dive into v1.2.6 reliability enhancements in
ARCHITECTURE_ANALYSIS.md. - Concurrency: Defined new serial-queue threading contract in
CONCURRENCY.md. - Migration: Step-by-step guide for using new FGS features.
📦 Installation
Update your pubspec.yaml:
dependencies:
native_workmanager: ^1.2.6Note for Android: To ensure tasks survive app kills, please ensure your Application class implements Configuration.Provider as described in our Android Setup Guide.