Skip to content

fix: marker series issue rebuild#476

Merged
behnam-deriv merged 3 commits into
masterfrom
fix-marker-rebuild-issue
Jun 19, 2026
Merged

fix: marker series issue rebuild#476
behnam-deriv merged 3 commits into
masterfrom
fix-marker-rebuild-issue

Conversation

@ahrar-deriv

@ahrar-deriv ahrar-deriv commented May 26, 2026

Copy link
Copy Markdown
Collaborator

Clickup link:
Fixes issue: #

This PR contains the following changes:

  • ✨ New feature (non-breaking change which adds functionality)
  • 🛠️ Bug fix (non-breaking change which fixes an issue)
  • ❌ Breaking change (fix or feature that would cause existing functionality to change)
  • 🧹 Code refactor
  • ✅ Build configuration change
  • 📝 Documentation
  • 🗑️ Chore

Developers Note (Optional)

Pre-launch Checklist (For PR creator)

  • marker issue rebuild (subtree)
  • update intl for latest flutter use

As a creator of this PR:

  • ✍️ I have included clickup id and package/app_name in the PR title.
  • 👁️ I have gone through the code and removed any temporary changes (commented lines, prints, debug statements etc.).
  • ⚒️ I have fixed any errors/warnings shown by the analyzer/linter.
  • 📝 I have added documentation, comments and logging wherever required.
  • 🧪 I have added necessary tests for these changes.
  • 🔎 I have ensured all existing tests are passing.

Reviewers

Pre-launch Checklist (For Reviewers)

As a reviewer I ensure that:

  • ✴️ This PR follows the standard PR template.
  • 🪩 The information in this PR properly reflects the code changes.
  • 🧪 All the necessary tests for this PR's are passing.

Pre-launch Checklist (For QA)

  • 👌 It passes the acceptance criteria.

Pre-launch Checklist (For Maintainer)

  • [MAINTAINER_NAME] I make sure this PR fulfills its purpose.

Summary by Sourcery

Ensure chart marker areas fully rebuild when marker series change and update intl dependencies for compatibility with latest Flutter.

Bug Fixes:

  • Force remount of the main chart layout and marker area subtree when the markerSeries changes to prevent stale markers after trade-type or data switches.

Enhancements:

  • Wrap the marker area in a keyed subtree to better isolate and refresh marker rendering.

Build:

  • Update intl package to version 0.20.2 and add an override in the example app to align with the latest Flutter tooling.

@sourcery-ai

sourcery-ai Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Forces the chart widget tree to rebuild when the marker series changes by keying both the LayoutBuilder and the marker area subtree, and updates intl dependencies to 0.20.2 (including an override for the example app) for compatibility with newer Flutter versions.

File-Level Changes

Change Details Files
Ensure marker-related subtree is remounted when markerSeries changes to fix stale markers when switching trade types.
  • Added an ObjectKey based on markerSeries to the MainChart LayoutBuilder so the whole chart subtree is remounted when markerSeries identity changes.
  • Wrapped _buildMarkerArea content in a KeyedSubtree keyed by markerSeries to force rebuilding of the MarkerArea subtree when markerSeries changes.
  • Preserved existing MultipleAnimatedBuilder usage and AnimationInfo wiring while nesting it under the new KeyedSubtree.
lib/src/deriv_chart/chart/main_chart.dart
Update intl dependency to the latest compatible version and ensure the example app uses the same version.
  • Bumped intl dependency from ^0.19.0 to ^0.20.2 in the main package pubspec.yaml.
  • Added a dependency_overrides section in the example app pubspec.yaml to pin intl to 0.20.2 for Flutter compatibility.
pubspec.yaml
example/pubspec.yaml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@claude

claude Bot commented May 26, 2026

Copy link
Copy Markdown

Claude finished @ahrar-deriv's task —— View job


PR Review: fix: marker series issue rebuild

I've reviewed this PR that fixes the marker series rebuild issue. Here's my comprehensive analysis:


Summary

Approach:APPROVED with minor suggestions

The fix correctly addresses the marker persistence issue after trade-type switches by using ObjectKey(widget.markerSeries) to force widget remounting when the marker series changes. This is a solid Flutter pattern for ensuring proper widget lifecycle management.


Detailed Review

🟢 High Priority - Correctness

1. Key-based remounting strategy (main_chart.dart:406, 587-588)

Good approach - Using ObjectKey(widget.markerSeries) in two places:

  • On the LayoutBuilder (line 406) to force the entire main chart layout rebuild
  • On the KeyedSubtree wrapping _buildMarkerArea() (line 587)

Why this works:

  • ObjectKey uses identity comparison (identical()), so when markerSeries changes to a different instance, Flutter destroys the old widget tree and creates a new one
  • This ensures MarkerArea's initState() runs again, properly re-registering gesture callbacks with the new series data
  • Prevents stale marker state from persisting across trade-type switches

Comment on implementation: The double-keying (both LayoutBuilder and KeyedSubtree) might be redundant. The LayoutBuilder key already forces a full remount of everything inside, making the inner KeyedSubtree key potentially unnecessary. However, this is defensive programming and won't cause issues - just slightly more explicit.


🟡 Medium Priority - Code Quality

2. Dependency update (pubspec.yaml:20, example/pubspec.yaml:31)

Acceptable - Updating intl from ^0.19.0 to ^0.20.2

  • The override in example/pubspec.yaml ensures compatibility with latest Flutter tooling
  • Minor version bump should be safe (0.19 → 0.20)

⚠️ Concern: The dependency review bot flagged "Unknown License" for the intl package. This is likely a false positive (intl is a Dart team package with BSD license), but worth verifying.

Suggestion: Add a note in the PR description about why this intl update was needed (e.g., "Required for Flutter 3.24+ compatibility" or similar).


🟢 Low Priority - Edge Cases & Testing

3. Null marker series handling

Safe - The code already has null-safety checks:

  • if (widget.markerSeries != null) _buildMarkerArea() (line 434)
  • When markerSeries is null, ObjectKey(null) will work correctly

4. Test coverage

⚠️ Unknown - The PR checklist shows:

  • 🧪 I have added necessary tests for these changes
  • 🔎 I have ensured all existing tests are passing

Recommendation:

  • Add a test verifying marker area rebuilds when markerSeries changes
  • Test scenario: Switch between different MarkerSeries instances and verify no stale markers persist
  • Verify gesture callbacks are properly re-registered after series change

🔵 Performance Considerations

5. Rebuild efficiency

Acceptable - The key-based approach triggers full rebuilds, but:

  • This only happens when markerSeries actually changes (trade-type switches)
  • Not a frequent operation in typical usage
  • The alternative (trying to update state in place) would be more error-prone

6. GestureManager callback lifecycle (marker_area.dart:49, 55)

Proper cleanup - MarkerArea already:

  • Registers callback in initState() (line 49-50)
  • Removes callback in dispose() (line 55)
  • The forced remount ensures old callbacks are cleaned up properly

Specific Code Comments

lib/src/deriv_chart/chart/main_chart.dart:402-406

@override
Widget build(BuildContext context) => LayoutBuilder(
      // Force remount when markerSeries changes.
      key: ObjectKey(widget.markerSeries),

✅ Excellent documentation in the comment. Consider mentioning this is for preventing gesture callback issues:

// Force remount when markerSeries changes.
// This ensures MarkerArea's initState() re-runs, properly re-registering
// gesture callbacks and preventing stale markers after trade-type switches.

lib/src/deriv_chart/chart/main_chart.dart:587-603

The KeyedSubtree wrapper is well-implemented. Minor optimization suggestion: Since the LayoutBuilder above already has the same key, you could remove this inner KeyedSubtree to reduce nesting:

Current:

Widget _buildMarkerArea() => KeyedSubtree(
      key: ObjectKey(widget.markerSeries),
      child: MultipleAnimatedBuilder(
        // ...
      ),
    );

Alternative (simpler):

Widget _buildMarkerArea() => MultipleAnimatedBuilder(
      animations: <Listenable>[
        currentTickAnimation,
        topBoundQuoteAnimationController,
        bottomBoundQuoteAnimationController
      ],
      builder: (BuildContext context, _) => MarkerArea(
        markerSeries: widget.markerSeries!,
        quoteToCanvasY: chartQuoteToCanvasY,
        animationInfo: AnimationInfo(
          currentTickPercent: currentTickAnimation.value,
        ),
      ),
    );

However, keeping both keys is defensive and won't hurt performance.


Security

No security concerns - This is a UI rendering fix with no security implications.


Checklist Review

Based on the PR template:

Missing items:

  • ✍️ Clickup ID not included in PR title
  • 🧪 Test coverage not mentioned
  • 🔎 Test passing status not confirmed

Recommendation: Update the PR checklist before merging.


Recommendations

Must-have before merge:

  1. ✅ Run flutter analyze --no-fatal-infos to ensure no new warnings
  2. ✅ Run flutter test to verify all tests pass
  3. ✅ Manually test trade-type switching to verify markers update correctly

Nice-to-have:

  1. Add regression test for marker series switching
  2. Clarify in PR description why intl update was needed
  3. Consider removing redundant inner KeyedSubtree (or document why both keys are needed)

Final Verdict

Status:Approve with minor suggestions

This is a well-targeted bug fix with proper documentation. The approach is sound and follows Flutter best practices for forcing widget remounts. The main concerns are around test coverage verification and completing the PR checklist.

Priority breakdown:

  • 🟢 High priority items: 0 blocking issues
  • 🟡 Medium priority items: 1 (test coverage verification)
  • 🔵 Low priority items: 2 (optional optimizations)

@github-actions

github-actions Bot commented May 26, 2026

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

OpenSSF Scorecard

PackageVersionScoreDetails

Scanned Manifest Files

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • You’re using ObjectKey(widget.markerSeries) both on the LayoutBuilder and inside _buildMarkerArea; consider whether you really need to force a full MainChart remount on markerSeries changes or if scoping the key just to the marker subtree is sufficient to avoid unnecessary rebuild cost.
  • If markerSeries has a stable identifier (e.g. an ID or value-based equality), switching from ObjectKey to a ValueKey based on that identifier would make the remount behavior more predictable and less tied to object identity.
  • Now that the root pubspec.yaml has intl: ^0.20.2, verify whether the dependency_overrides for intl in the example app is still needed, as keeping it may hide version conflicts that would otherwise surface.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- You’re using `ObjectKey(widget.markerSeries)` both on the `LayoutBuilder` and inside `_buildMarkerArea`; consider whether you really need to force a full `MainChart` remount on markerSeries changes or if scoping the key just to the marker subtree is sufficient to avoid unnecessary rebuild cost.
- If `markerSeries` has a stable identifier (e.g. an ID or value-based equality), switching from `ObjectKey` to a `ValueKey` based on that identifier would make the remount behavior more predictable and less tied to object identity.
- Now that the root `pubspec.yaml` has `intl: ^0.20.2`, verify whether the `dependency_overrides` for `intl` in the example app is still needed, as keeping it may hide version conflicts that would otherwise surface.

## Individual Comments

### Comment 1
<location path="lib/src/deriv_chart/chart/main_chart.dart" line_range="587-589" />
<code_context>
-          quoteToCanvasY: chartQuoteToCanvasY,
-          animationInfo: AnimationInfo(
-            currentTickPercent: currentTickAnimation.value,
+  Widget _buildMarkerArea() => KeyedSubtree(
+        key: ObjectKey(widget.markerSeries),
+        child: MultipleAnimatedBuilder(
+          animations: <Listenable>[
+            currentTickAnimation,
</code_context>
<issue_to_address>
**suggestion:** Re-evaluate the need for both LayoutBuilder key and KeyedSubtree keyed by markerSeries.

Both the `LayoutBuilder` (in `build`) and this `KeyedSubtree` use `ObjectKey(widget.markerSeries)`, causing the same remount trigger at two levels. This can cause unnecessary state resets higher in the tree and makes remount behavior harder to understand. Consider keying only the minimal subtree that truly depends on `markerSeries` (likely this `MarkerArea`/`KeyedSubtree`) and leaving the outer `LayoutBuilder` unkeyed unless you explicitly need to rebuild the entire layout on `markerSeries` changes.

Suggested implementation:

```
    return LayoutBuilder(
      builder: (BuildContext context, BoxConstraints constraints) {

```

- The `_buildMarkerArea` method that wraps the marker area in a `KeyedSubtree(key: ObjectKey(widget.markerSeries), ...)` should remain as it is; that subtree is now solely responsible for remounting on `markerSeries` changes.
- If the exact `LayoutBuilder` snippet differs (extra parameters, different formatting), apply the same change conceptually: remove only the `key: ObjectKey(widget.markerSeries),` argument from the `LayoutBuilder` constructor, keeping the rest of the arguments and formatting intact.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +587 to +589
Widget _buildMarkerArea() => KeyedSubtree(
key: ObjectKey(widget.markerSeries),
child: MultipleAnimatedBuilder(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Re-evaluate the need for both LayoutBuilder key and KeyedSubtree keyed by markerSeries.

Both the LayoutBuilder (in build) and this KeyedSubtree use ObjectKey(widget.markerSeries), causing the same remount trigger at two levels. This can cause unnecessary state resets higher in the tree and makes remount behavior harder to understand. Consider keying only the minimal subtree that truly depends on markerSeries (likely this MarkerArea/KeyedSubtree) and leaving the outer LayoutBuilder unkeyed unless you explicitly need to rebuild the entire layout on markerSeries changes.

Suggested implementation:

    return LayoutBuilder(
      builder: (BuildContext context, BoxConstraints constraints) {

  • The _buildMarkerArea method that wraps the marker area in a KeyedSubtree(key: ObjectKey(widget.markerSeries), ...) should remain as it is; that subtree is now solely responsible for remounting on markerSeries changes.
  • If the exact LayoutBuilder snippet differs (extra parameters, different formatting), apply the same change conceptually: remove only the key: ObjectKey(widget.markerSeries), argument from the LayoutBuilder constructor, keeping the rest of the arguments and formatting intact.

@claude

claude Bot commented Jun 19, 2026

Copy link
Copy Markdown

Claude encountered an error —— View job


PR Review: fix: marker series issue rebuild

Starting review of the marker rebuild fix...

Tasks:

  • Read changed files and understand the implementation
  • Analyze the fix approach for correctness
  • Evaluate performance and edge cases
  • Assess the sourcery-ai feedback about double-keying
  • Provide comprehensive review with priority items

@claude

claude Bot commented Jun 19, 2026

Copy link
Copy Markdown

Claude finished @ahrar-deriv's task —— View job


PR Review: fix: marker series issue rebuild

Starting review of the marker rebuild fix...

Tasks:

  • Read changed files and understand the implementation
  • Analyze the fix approach for correctness
  • Evaluate the double-keying concern from sourcery-ai
  • Check for edge cases and performance implications
  • Review test coverage needs
  • Provide comprehensive prioritized feedback

Summary

This PR fixes a marker persistence bug where markers weren't properly updating after trade-type switches. The core fix adds ObjectKey(widget.markerSeries) to force widget remounting when markerSeries changes. The approach is sound, but sourcery-ai raises a valid concern about redundant double-keying.

Current state: The intl dependency changes mentioned in the PR description were reverted in commit 4082970, so only the marker fix remains in this PR.


High Priority Issues

1. Redundant Double-Keying (main_chart.dart:406, 588)

Issue: The PR adds ObjectKey(widget.markerSeries) in TWO places:

  • On the LayoutBuilder (line 406) - forces entire MainChart layout remount
  • On the KeyedSubtree inside _buildMarkerArea() (line 588) - forces MarkerArea subtree remount

Problem: When markerSeries changes, BOTH keys trigger, causing the same remount at two different tree levels. This is redundant because:

  1. The outer LayoutBuilder key already forces everything inside to remount (including MarkerArea)
  2. Having both keys makes the widget lifecycle harder to reason about
  3. Potentially causes unnecessary state resets for widgets higher in the tree that don't depend on markerSeries

Why it still works: ObjectKey uses identity comparison (identical()), so when markerSeries changes, both keys fire and force remount. This ensures MarkerArea.initState() runs again, properly re-registering the tap gesture callback (marker_area.dart:49-50).

Sourcery-ai's recommendation is correct: Keep ONLY the KeyedSubtree key inside _buildMarkerArea() and remove the key from LayoutBuilder. This is more targeted and follows Flutter best practices of keying the minimal subtree that actually depends on the changing data.

Suggested fix:

@override
Widget build(BuildContext context) => LayoutBuilder(
      // Remove the key from here
      builder: (BuildContext context, BoxConstraints constraints) {
        // ... rest of the code

The KeyedSubtree key at line 588 is sufficient to solve the marker persistence issue.


Medium Priority Issues

2. ObjectKey vs ValueKey Consideration

Observation: The fix uses ObjectKey(widget.markerSeries) which relies on object identity (reference equality).

Potential issue: If MarkerSeries is recreated with the same content but different object reference, this will trigger a remount even when the data hasn't actually changed.

Question: Does MarkerSeries have a stable identifier (like an ID field) or implement value-based equality?

If yes: Consider using ValueKey(widget.markerSeries?.id) or ValueKey(widget.markerSeries) (if it has proper == override) for more predictable behavior.

If no: ObjectKey is appropriate, but document this trade-off: remount happens on any reference change, even if content is identical.


3. Missing Test Coverage

Issue: No tests found for marker series rebuilding behavior.

Risk: Without tests, this bug could regress in future refactorings.

Recommended test scenarios:

testWidgets('MarkerArea remounts when markerSeries changes', (tester) async {
  // 1. Render chart with markerSeries A
  // 2. Switch to markerSeries B
  // 3. Verify old markers don't persist
  // 4. Verify gesture callbacks work correctly with new series
});

testWidgets('MarkerArea handles null markerSeries', (tester) async {
  // Verify ObjectKey(null) works correctly
});

Low Priority / Informational

4. Comment Quality

The comment at lines 402-405 is excellent:

// Force remount when markerSeries changes.
// LayoutBuilder only re-invokes its builder on constraint changes;
// without a key that tracks markerSeries identity, the old
// MarkerArea subtree persists after trade-type switches.

Suggestion: If keeping only the KeyedSubtree key (as recommended), move/adapt this comment to the _buildMarkerArea() method.


5. Null Safety

The code properly handles widget.markerSeries == null:

  • Conditional rendering at line 434: if (widget.markerSeries != null) _buildMarkerArea()
  • ObjectKey(null) is valid in Flutter and won't cause issues

6. Performance Impact

Rebuild cost: The remount only triggers when markerSeries actually changes (typically during trade-type switches), not on every frame.

Optimization: Using only the KeyedSubtree key (instead of both) will reduce unnecessary rebuilds of sibling widgets in the Stack.


7. Intl Dependency Changes ✅ (No longer in PR)

The intl version bump from ^0.19.0 to ^0.20.2 mentioned in the PR description was reverted in commit 4082970, so the current PR only contains the marker fix. The PR description should be updated to reflect this.


Detailed Code Analysis

MarkerArea Lifecycle (marker_area.dart:47-56)

The fix correctly addresses this lifecycle issue:

Problem scenario:

  1. User switches trade types → new markerSeries object passed to MainChart
  2. Without keys, Flutter reuses existing MarkerArea widget (since type matches)
  3. MarkerArea.initState() doesn't run again
  4. Old gesture callback still registered with GestureManager
  5. Old callback references old markerSeries → stale markers persist

How the fix works:

// In initState:
gestureManager = context.read<GestureManagerState>()
  ..registerCallback(_onTap);  // line 49-50

// In dispose:
gestureManager.removeCallback(_onTap);  // line 55

When ObjectKey(widget.markerSeries) changes:

  1. Flutter destroys old MarkerArea widget
  2. dispose() runs → removes old callback
  3. New MarkerArea created with new markerSeries
  4. initState() runs → registers new callback with correct markerSeries reference

Recommendations

Must Address Before Merge:

  1. Remove the redundant LayoutBuilder key (keep only KeyedSubtree key) per sourcery-ai's recommendation
  2. Update PR description to clarify that intl changes were reverted

Should Address:

  1. Add regression tests for marker series switching
  2. Consider ValueKey if MarkerSeries has stable identifiers

Nice to Have:

  1. Move the explanatory comment to _buildMarkerArea() if removing LayoutBuilder key

Final Verdict

Status:Approve with changes recommended

The fix correctly solves the marker persistence bug, but the implementation can be improved by removing the redundant outer key. The core approach of using keyed widgets to force remounting is sound Flutter practice.

Priority Breakdown:

  • 🔴 High: 1 issue (redundant double-keying)
  • 🟡 Medium: 2 issues (ValueKey consideration, test coverage)
  • 🟢 Low: 5 observations (mostly positive)

The PR is functionally correct but could be more idiomatic. The suggested changes improve code quality without changing the fix's effectiveness.


@behnam-deriv behnam-deriv merged commit 98ea7b6 into master Jun 19, 2026
7 checks passed
@behnam-deriv behnam-deriv deleted the fix-marker-rebuild-issue branch June 19, 2026 06:10
@behnam-deriv behnam-deriv restored the fix-marker-rebuild-issue branch June 19, 2026 06:10
behnam-deriv added a commit that referenced this pull request Jun 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants