v1.4.5: fix #57 chain placeholder data flow + kmpworkmanager 3.2.0 (Android FGS permission opt-in) - #58
Merged
vietnguyentuan2019 merged 2 commits intoAug 6, 2026
Conversation
Both platforms: the documented {{task_id.output_key}} chain placeholder
syntax never actually worked.
iOS root cause:
- Step results were stored under flat, unprefixed keys, but substitution
looked up the whole "taskId.key" string as one key — the two never
matched, so placeholders always stayed literal.
- A parallel step's tasks overwrote each other's stored result; only the
last task to finish survived.
- AnyCodable's Codable conformance had no Int32/Int64/Float/UInt64 cases,
so any worker whose result data included one of those types (e.g.
ImageProcessWorker's originalSize/processedSize) silently failed to
persist via the try?-guarded saveStepResult call — the next step's
substitution data came back empty with no error surfaced anywhere.
Android had no substitution mechanism at all. Its chains were built by
enqueuing every step's WorkRequest upfront via WorkManager's native
.then() chaining, which freezes each step's config before any earlier
step has even run — there was no point in time a later step could see a
real predecessor output. Fixed by moving to a dynamic per-step enqueue
(ChainHelper.buildAndEnqueueStep) driven by WorkInfo completion, with
output captured via a new ChainResultCapturingWorker decorator (WorkInfo
data is always empty coming out of BaseKmpWorker) and resumed
idempotently via enqueueUniqueWork(..., KEEP).
Both platforms now: namespace each task's result under "<taskId>.<key>",
merge (don't overwrite) across parallel tasks, and resolve a whole-match
placeholder (the entire config value is one {{...}}) to the original
typed value rather than a stringified one, so substitution can target
numeric/bool fields, not just strings.
Also fixes the task_chain.dart doc example, which had been demonstrating
the exact same key-name bug (used the input key savePath instead of the
output key filePath) — and expands doc/use-cases/06-chain-processing.md
with a full data-flow section.
Tests: 10 new Kotlin unit tests (ChainHelperSubstitutionTest), 3 new
device integration tests (issue_57 group, passing on real Pixel 6 Pro +
iPhone 17 sim), full pre-existing Task Chains + Chain data flow groups
re-verified green on both platforms.
…issions opt-in
Bumps kmpworkmanager 3.1.0 -> 3.2.0 (upstream fix for Play Store rejection
on apps that never use isHeavyTask — the core lib no longer force-merges
FOREGROUND_SERVICE / FOREGROUND_SERVICE_DATA_SYNC into every consumer app's
manifest). Rebuilt KMPWorkManager.xcframework from kmpworkmanager v3.2.0
source via scripts/build-xcframework.sh, updated Package.swift/podspec
url+checksum to point at the v1.4.5 release asset (not yet uploaded).
This plugin's OWN android/src/main/AndroidManifest.xml had the identical
problem independently (unconditional FOREGROUND_SERVICE declaration, not
inherited from kmpworkmanager) — fixed the same way: removed the
unconditional permissions/service-type override, documented the opt-in
snippet consumer apps need if they use isHeavyTask in
doc/ANDROID_SETUP.md and doc/PRODUCTION_GUIDE.md. Added ManifestGuardTest
(mirrors kmpworkmanager's own test) so this can't silently regress.
native_workmanager_gen: widened analyzer constraint >=10.0.0 <14.0.0 to
<15.0.0 — the upper bound was stale, verified against analyzer 14.1.0
(15/15 generator tests pass, pana 160/160).
Verification: pana 160/160 both packages; 1316 Dart unit/integration/
security/performance tests pass; issue_57 chain-placeholder device tests
(3/3) and FGS device tests (2/2) pass on real Pixel 6 Pro + iPhone 17 sim
with the new xcframework; demo app launches clean on both platforms with
no crashes (verified via logcat / simulator console — KmpWorkManager
initializes, a real WorkManager task runs successfully on first launch).
Control experiment confirmed an unrelated flaky device test
('missing input key' timeout when run via --plain-name filter in
isolation) is a pre-existing environment issue, reproduced identically on
the pre-3.2.0 code — not a regression from this bump.
Not yet done: GitHub release v1.4.5 + xcframework asset upload (needed
before Package.swift's binaryTarget URL resolves) and the SPM layer-2/3/4
scratch-app verification from CONTRIBUTING.md, which requires that
release to exist. Held pending explicit publish approval.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What's in this PR
Two pieces of work, shipped together as v1.4.5 (per maintainer decision — single release, not split):
1. Fix #57 —
{{taskId.outputKey}}chain data-flow placeholdersThe documented syntax for passing one chain step's output into a later step's config never actually worked on either platform.
"taskId.key"string as one key — never matched. Parallel steps also overwrote each other's stored result. Separately,AnyCodablehad noInt32/Int64/Float/UInt64cases, so any worker result containing one of those types silently failed to persist.WorkRequestupfront via WorkManager's native.then()chaining, before any earlier step had run. Rearchitected to dynamic per-step enqueue (ChainHelper.buildAndEnqueueStep) driven byWorkInfocompletion, with output captured via a newChainResultCapturingWorkerdecorator and resumed idempotently viaenqueueUniqueWork(..., KEEP)."<taskId>.<key>", merge (not overwrite) across parallel tasks, and resolve whole-match placeholders to typed values instead of always stringifying.Also fixes a doc bug in
task_chain.dart's own example, which demonstrated this exact bug (used the input keysavePathinstead of the output keyfilePath).2. kmpworkmanager 3.1.0 → 3.2.0 + Android FGS permission opt-in
isHeavyTask— the core lib no longer force-mergesFOREGROUND_SERVICE/FOREGROUND_SERVICE_DATA_SYNCinto every consumer manifest). RebuiltKMPWorkManager.xcframeworkfrom source, re-verified against the full test suite.android/src/main/AndroidManifest.xmlhad the identical problem independently (not inherited from kmpworkmanager) — fixed the same way, with a newManifestGuardTestso it can't regress. Documented the opt-in snippet indoc/ANDROID_SETUP.md/doc/PRODUCTION_GUIDE.md.native_workmanager_gen: widened staleanalyzerconstraint (<14.0.0→<15.0.0), verified against 14.1.0.Testing
pana: 160/160 both packagesChainHelperSubstitutionTest, 1 newManifestGuardTest)Not yet done (release-gated, tracked separately)
GitHub release
v1.4.5+ xcframework asset upload, full SPM layer 2-4 scratch-app verification (requires the release to exist first — SPM only resolveshttpsbinary target URLs),pub publishfor both packages.