Repository navigation
1.3.0: serialize GanttController fetches and add GanttActivityAction style - #31
Conversation
…ently GanttController.fetch() could run multiple overlapping fetches at once (e.g. the automatic fetch on mount racing a caller's own refetch triggered by async filter restoration), and whichever RPC resolved last would win regardless of which fetch was triggered last — every consumer had to guard against a stale response overwriting a fresher one on its own. fetch() now tracks whether a fetch is already in flight; a call that arrives while one is running just marks a pending flag and returns, and the controller reruns exactly once, immediately after, picking up whatever changed in the meantime. At most one fetch is ever in flight, so there is nothing left to race against. Fetch listeners changed from VoidCallback to FutureOr<void> Function() so the controller can await them — backward compatible, a plain synchronous callback still satisfies the new type. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
@RichiB20 Were you able to try it out or check it? |
Iterate over a snapshot of the listener list so a listener added or removed while a fetch is suspended on an await no longer throws a ConcurrentModificationError. A Gantt widget disposed while its data is still loading hits this on its own dispose(), which removes its fetch listener mid-run. Wrap the run in try/finally and catch per listener: a listener that throws no longer leaves _fetchInFlight stuck at true, which turned every later fetch() into a silent no-op and left the chart unable to reload, and no longer skips the remaining listeners. Failures are reported through FlutterError.reportError instead of being swallowed as an unhandled async error. Add test/gantt_controller_fetch_test.dart covering fetch coalescing, awaiting of async listeners, listener add/remove during an in-flight fetch, and listener failures. Seven of the twelve tests fail against the previous implementation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document the listener error handling and the ConcurrentModificationError fix, and set the release date. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
What Makes GanttController.fetch() safe to call at any time, from anywhere, without callers having to reason about concurrency:
Adds test/gantt_controller_fetch_test.dart (12 tests) and bumps the package to 1.3.0. Why fetch() used to fire all listeners at once, with no coordination between calls. Three problems followed from that:
How GanttController tracks two flags, _fetchInFlight and _fetchPending. fetch() starts a run only when none is active; a call arriving during a run just sets the pending flag and returns. When the run finishes, it reruns exactly once if anything was requested in the meantime, so N calls during a fetch collapse into a single follow-up rather than N parallel fetches. The dispatch loop itself is hardened in two ways:
Fetch listeners changed type from VoidCallback to FutureOr Function() so the controller can await them. This is not a breaking change: FutureOr normalizes to void in Dart's type system, so the two function types are equivalent and existing synchronous callbacks keep compiling unchanged. The observable difference is that asynchronous listeners are now awaited — which is the point of the fix. Note on scope: listeners within a single run are now awaited one after another rather than dispatched concurrently. For the single-listener case (the Gantt widget's own) this changes nothing. Testing
Local verification: dart format --set-exit-if-changed . → 16 files, 0 changed Heads-up for the reviewer: .github/workflows/flutter-analyze.yaml runs format, analyze and pana, but not flutter test — the new tests will not run in CI as things stand. LAST_CHANGES.md
|
Keeps the example app's resolved version in sync with the package version bump. Also picks up the transitive meta 1.17.0 -> 1.18.0 bump and the Dart SDK constraint refresh from the local toolchain. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
What does it do?
Serialized fetches
GanttController.fetch()now tracks whether a fetch is already in flight. A call that arrives while one is running sets a pending flag and returns; the controller then reruns exactly once, right after, picking up whatever changed in the meantime. At most one fetch is ever in flight.VoidCallbacktoFutureOr<void> Function()so the controller can await them. This is backward compatible: a plain synchronous callback still satisfies the new type.Hardened fetch
activitiesAsync/holidaysAsync) no longer leaves the controller permanently unable to fetch, and no longer skips the remaining listeners. The error is reported throughFlutterError.reportErrorinstead of being swallowed.Ganttwidget disposed while its data is still loading) no longer throws aConcurrentModificationError.Action button style
GanttActivityActionhas a new optionalstyle(ButtonStyle?), applied to the action'sIconButton. It is optional, so existing code is unaffected.Why is it needed?
How to test it?
flutter test: covers serialization, coalescing of concurrent calls, listener errors, and listeners added or removed during a fetch.style: ButtonStyle(foregroundColor: WidgetStateProperty.resolveWith((s) => s.contains(WidgetState.hovered) ? Colors.red : null))to aGanttActivityAction: the icon turns red on hover.Related issues/PRs
Bumps the version to 1.3.0;
CHANGELOG.mdis updated.