Skip to content

fix: marker rebuild issue - #482

Merged
behnam-deriv merged 2 commits into
masterfrom
fix-marker-rebuild-issue-scoped
Jun 24, 2026
Merged

fix: marker rebuild issue#482
behnam-deriv merged 2 commits into
masterfrom
fix-marker-rebuild-issue-scoped

Conversation

@behnam-deriv

@behnam-deriv behnam-deriv commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

Clickup link:
Fixes issue: #

This PR contains the following changes:

This fixes the marker rebuild issue in deriv mobile app , similar to approach in this branch in main_chart.dart , but with a targetted and scoped solution that only affects markers and avoid exccessive rebuild of the whole chart which was causing drawing tools feature to not function properly on web.

  • ✨ 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)

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

Bug Fixes:

  • Ensure marker area rebuilds correctly when marker series updates without impacting the rest of the chart.

@sourcery-ai

sourcery-ai Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor
Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Adds a LayoutBuilder with an ObjectKey around the marker series area in the main chart to force targeted rebuilds when markerSeries changes, preventing unnecessary full chart rebuilds and fixing marker-related issues on web.

Sequence diagram for markerSeries rebuild with LayoutBuilder

sequenceDiagram
    participant FlutterFramework
    participant ChartImplementationState
    participant LayoutBuilder
    participant MarkerArea

    FlutterFramework->>ChartImplementationState: build(context)
    ChartImplementationState->>LayoutBuilder: create with ObjectKey(markerSeries)
    FlutterFramework->>LayoutBuilder: builder(context, constraints)
    alt [markerSeries != null]
        LayoutBuilder->>ChartImplementationState: _buildMarkerArea()
        ChartImplementationState-->>MarkerArea: build marker widgets
    else [markerSeries == null]
        LayoutBuilder-->>LayoutBuilder: return SizedBox.shrink()
    end

    alt [markerSeries changes]
        FlutterFramework->>LayoutBuilder: rebuild subtree (ObjectKey changed)
        LayoutBuilder->>ChartImplementationState: _buildMarkerArea() or SizedBox.shrink()
    else [other chart parts unchanged]
        FlutterFramework-->>ChartImplementationState: no full chart rebuild
    end
Loading

File-Level Changes

Change Details Files
Scope marker area rebuilds using LayoutBuilder and an ObjectKey keyed to markerSeries to avoid excessive chart rebuilds while still reacting to marker changes.
  • Wrap the marker area widget tree in a LayoutBuilder to isolate its rebuild behavior from the rest of the chart
  • Use an ObjectKey based on widget.markerSeries so the layout subtree is recreated when markerSeries identity changes
  • Replace the simple null-check conditional around _buildMarkerArea with a LayoutBuilder builder that returns either _buildMarkerArea or a SizedBox.shrink when markerSeries is null
lib/src/deriv_chart/chart/main_chart.dart

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

@github-actions

Copy link
Copy Markdown

Dependency Review

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

OpenSSF Scorecard

PackageVersionScoreDetails

Scanned Manifest Files

@claude

claude Bot commented Jun 23, 2026

Copy link
Copy Markdown

Claude encountered an error —— View job


I'll analyze this and get back to you.

@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 left some high level feedback:

  • Using a LayoutBuilder solely to attach an ObjectKey feels indirect; consider extracting _buildMarkerArea into its own Widget and giving that widget a Key instead so the intent (scoped rebuild of markers) is clearer and avoids an extra layout pass.
  • Relying on ObjectKey(widget.markerSeries) assumes markerSeries’s identity changes when its contents change; if markerSeries is mutated in place, a more explicit ValueKey (e.g. based on a version or hash) might be safer to ensure rebuilds happen when expected.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Using a `LayoutBuilder` solely to attach an `ObjectKey` feels indirect; consider extracting `_buildMarkerArea` into its own `Widget` and giving that widget a `Key` instead so the intent (scoped rebuild of markers) is clearer and avoids an extra layout pass.
- Relying on `ObjectKey(widget.markerSeries)` assumes `markerSeries`’s identity changes when its contents change; if markerSeries is mutated in place, a more explicit `ValueKey` (e.g. based on a version or hash) might be safer to ensure rebuilds happen when expected.

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.

@claude

claude Bot commented Jun 23, 2026

Copy link
Copy Markdown

Claude encountered an error —— View job


I'll analyze this and get back to you.

3 similar comments
@claude

claude Bot commented Jun 23, 2026

Copy link
Copy Markdown

Claude encountered an error —— View job


I'll analyze this and get back to you.

@claude

claude Bot commented Jun 24, 2026

Copy link
Copy Markdown

Claude encountered an error —— View job


I'll analyze this and get back to you.

@claude

claude Bot commented Jun 24, 2026

Copy link
Copy Markdown

Claude encountered an error —— View job


I'll analyze this and get back to you.

@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown

Claude finished @behnam-deriv's task in 1m 50s —— View job


🤖 Claude PR Review Complete

Model: claude-sonnet-4-6 | Review Type: 📋 Initial Review

Summary

This PR makes a targeted fix to force marker area rebuilds when markerSeries changes identity, by wrapping the marker area in a LayoutBuilder with an ObjectKey. The approach is functionally correct and effectively avoids full chart rebuilds (which were breaking drawing tools on web). However, using LayoutBuilder solely to attach a key is semantically incorrect in Flutter — LayoutBuilder triggers an additional layout pass and provides constraints that are completely ignored ((_, __)) here. Flutter provides KeyedSubtree precisely for this use case with no layout overhead.

Recommendation: REQUEST CHANGES — the fix is conceptually right but uses the wrong Flutter primitive, adding unnecessary layout cost in a high-frequency rendering context.


🟡 Medium Priority Issues

🟡 1. LayoutBuilder used solely as a key holder — use KeyedSubtree insteadlib/src/deriv_chart/chart/main_chart.dart:429-434

Details

Severity File Lines
MEDIUM lib/src/deriv_chart/chart/main_chart.dart 429-434

❌ Problematic Code:

LayoutBuilder(
  key: ObjectKey(widget.markerSeries),
  builder: (_, __) => widget.markerSeries != null
      ? _buildMarkerArea()
      : const SizedBox.shrink(),
),

📋 Issue: LayoutBuilder triggers a full layout pass and exposes BoxConstraints to its builder. Here the constraints are discarded entirely (_, __), which means the only purpose of LayoutBuilder is to be a container for the ObjectKey. Flutter provides KeyedSubtree specifically for this pattern — it associates a key with a subtree without any layout overhead.

⚠️ Impact: In a financial chart that repaints at high frequency (every tick/animation frame), an unnecessary layout pass per frame adds measurable rendering cost. Charts are already layout-heavy; adding gratuitous layout nodes degrades performance, especially on lower-end mobile devices.

✅ Fix:

KeyedSubtree(
  key: ObjectKey(widget.markerSeries),
  child: widget.markerSeries != null
      ? _buildMarkerArea()
      : const SizedBox.shrink(),
),

💡 Explanation: KeyedSubtree is the canonical Flutter widget for attaching a key to a subtree. It has no layout behavior — it simply forwards the child — so it achieves the same forced-recreate semantics on key change without the extra layout pass. This is the correct primitive here.


🟢 Low Priority Issues

🟢 2. Marker placeholder always occupies a Stack slot when markerSeries is nulllib/src/deriv_chart/chart/main_chart.dart:429-434

Details

Severity File Lines
LOW lib/src/deriv_chart/chart/main_chart.dart 429-434

❌ Problematic Code:

LayoutBuilder(
  key: ObjectKey(widget.markerSeries),
  builder: (_, __) => widget.markerSeries != null
      ? _buildMarkerArea()
      : const SizedBox.shrink(),
),

📋 Issue: The previous code used if (widget.markerSeries != null) _buildMarkerArea(), which means when markerSeries is null, no widget was added to the Stack's children at all. The new code always adds a widget (a SizedBox.shrink() placeholder) to the Stack's children list when null. This is a subtle behavioral change — a zero-size widget is still a node in the widget tree that participates in hit testing and layout.

⚠️ Impact: Minor: one extra SizedBox.shrink() node in the Stack when markers are not used. The impact is negligible in most cases, but worth being intentional about it.

✅ Fix:

If you still want to key the subtree, you can preserve the if-guard:

if (widget.markerSeries != null)
  KeyedSubtree(
    key: ObjectKey(widget.markerSeries),
    child: _buildMarkerArea(),
  ),

This removes the null branch entirely while still keying the subtree when markers exist.

💡 Explanation: Keeping the if-guard from before means no widget is added when there are no markers, preserving the original behavior. The key on KeyedSubtree still forces recreation when markerSeries changes to a new object.


Summary Table

Priority Count Categories
🔴 Critical 0
🟠 High 0
🟡 Medium 1 Wrong Flutter primitive (LayoutBuilder vs KeyedSubtree)
🟢 Low 1 Unnecessary placeholder in Stack when markerSeries is null

Recommendations

  1. Replace LayoutBuilder with KeyedSubtree (Issue Phase 1 initial #1) — this is a one-line change and the semantically correct Flutter primitive for this use case.
  2. Consider combining the if-guard with KeyedSubtree (Issue Ramin/animate_to_current_tick #2) to preserve the original "no widget in Stack when null" behavior.
  3. The ObjectKey(widget.markerSeries) strategy is correct as long as markerSeries is replaced with a new object (not mutated in place) when its data changes — the existing immutability convention in this codebase supports this, so no change needed there.

Auto Fix Claude Reviews

Action Open Dashboard

@github-actions

Copy link
Copy Markdown

🤖 Claude PR Review Complete

Model: claude-sonnet-4-6 | Review Type: 📋 Initial Review

Summary

This PR makes a targeted fix to force marker area rebuilds when markerSeries changes identity, by wrapping the marker area in a LayoutBuilder with an ObjectKey. The approach is functionally correct and effectively avoids full chart rebuilds (which were breaking drawing tools on web). However, using LayoutBuilder solely to attach a key is semantically incorrect in Flutter — LayoutBuilder triggers an additional layout pass and provides constraints that are completely ignored ((_, __)) here. Flutter provides KeyedSubtree precisely for this use case with no layout overhead.

Recommendation: REQUEST CHANGES — the fix is conceptually right but uses the wrong Flutter primitive, adding unnecessary layout cost in a high-frequency rendering context.


🟡 Medium Priority Issues

🟡 1. LayoutBuilder used solely as a key holder — use KeyedSubtree insteadlib/src/deriv_chart/chart/main_chart.dart:429-434

Details

Severity File Lines
MEDIUM lib/src/deriv_chart/chart/main_chart.dart 429-434

❌ Problematic Code:

LayoutBuilder(
  key: ObjectKey(widget.markerSeries),
  builder: (_, __) => widget.markerSeries != null
      ? _buildMarkerArea()
      : const SizedBox.shrink(),
),

📋 Issue: LayoutBuilder triggers a full layout pass and exposes BoxConstraints to its builder. Here the constraints are discarded entirely (_, __), which means the only purpose of LayoutBuilder is to be a container for the ObjectKey. Flutter provides KeyedSubtree specifically for this pattern — it associates a key with a subtree without any layout overhead.

⚠️ Impact: In a financial chart that repaints at high frequency (every tick/animation frame), an unnecessary layout pass per frame adds measurable rendering cost. Charts are already layout-heavy; adding gratuitous layout nodes degrades performance, especially on lower-end mobile devices.

✅ Fix:

KeyedSubtree(
  key: ObjectKey(widget.markerSeries),
  child: widget.markerSeries != null
      ? _buildMarkerArea()
      : const SizedBox.shrink(),
),

💡 Explanation: KeyedSubtree is the canonical Flutter widget for attaching a key to a subtree. It has no layout behavior — it simply forwards the child — so it achieves the same forced-recreate semantics on key change without the extra layout pass. This is the correct primitive here.


🟢 Low Priority Issues

🟢 2. Marker placeholder always occupies a Stack slot when markerSeries is nulllib/src/deriv_chart/chart/main_chart.dart:429-434

Details

Severity File Lines
LOW lib/src/deriv_chart/chart/main_chart.dart 429-434

❌ Problematic Code:

LayoutBuilder(
  key: ObjectKey(widget.markerSeries),
  builder: (_, __) => widget.markerSeries != null
      ? _buildMarkerArea()
      : const SizedBox.shrink(),
),

📋 Issue: The previous code used if (widget.markerSeries != null) _buildMarkerArea(), which means when markerSeries is null, no widget was added to the Stack's children at all. The new code always adds a widget (a SizedBox.shrink() placeholder) to the Stack's children list when null. This is a subtle behavioral change — a zero-size widget is still a node in the widget tree that participates in hit testing and layout.

⚠️ Impact: Minor: one extra SizedBox.shrink() node in the Stack when markers are not used. The impact is negligible in most cases, but worth being intentional about it.

✅ Fix:

If the keyed subtree fix from Issue #1 is adopted, this concern is moot if you still need the key to be stable. But if markers are never present for some chart instances, the if-guard can be preserved while still using the key:

if (widget.markerSeries != null)
  KeyedSubtree(
    key: ObjectKey(widget.markerSeries),
    child: _buildMarkerArea(),
  ),

This removes the null branch entirely while still keying the subtree when markers exist.

💡 Explanation: Keeping the if-guard from before means no widget is added when there are no markers, preserving the original behavior. The key on KeyedSubtree still forces recreation when markerSeries changes to a new object.


Summary Table

Priority Count Categories
🔴 Critical 0
🟠 High 0
🟡 Medium 1 Wrong Flutter primitive (LayoutBuilder vs KeyedSubtree)
🟢 Low 1 Unnecessary placeholder in Stack when markerSeries is null

Recommendations

  1. Replace LayoutBuilder with KeyedSubtree (Issue Phase 1 initial #1) — this is a one-line change and the semantically correct Flutter primitive for this use case.
  2. Consider combining the if-guard with KeyedSubtree (Issue Ramin/animate_to_current_tick #2) to preserve the original "no widget in Stack when null" behavior.
  3. The ObjectKey(widget.markerSeries) strategy is correct as long as markerSeries is replaced with a new object (not mutated in place) when its data changes — the existing immutability convention in this codebase (@immutable / const constructors) supports this, so no change needed there.

Auto Fix Claude Reviews

Action Open Dashboard

@behnam-deriv
behnam-deriv merged commit 849e30e into master Jun 24, 2026
7 checks passed
@behnam-deriv
behnam-deriv deleted the fix-marker-rebuild-issue-scoped branch June 24, 2026 02:47
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.

1 participant