Skip to content

[two_dimensional_scrollables] Fix crash when trailing pinned spans fall within the cache extent - #12719

Open
icartsh wants to merge 1 commit into
flutter:mainfrom
icartsh:fix-tds-trailing-pinned-last-visible
Open

[two_dimensional_scrollables] Fix crash when trailing pinned spans fall within the cache extent#12719
icartsh wants to merge 1 commit into
flutter:mainfrom
icartsh:fix-tds-trailing-pinned-last-visible

Conversation

@icartsh

@icartsh icartsh commented Sep 2, 2026

Copy link
Copy Markdown

RenderTableViewport._updateFirstAndLastVisibleCell binary searches for the last non-pinned span whose trailing offset reaches the target trailing pixel. When the whole table fits inside the viewport plus the cache extent that search finds nothing, and the code falls back to the last index of the metrics map:

if (_firstNonPinnedColumn != null) {
  _lastNonPinnedColumn ??= _columnMetrics.length - 1;
}

With trailingPinnedColumnCount (or trailingPinnedRowCount) greater than zero, that index is a pinned span. layoutChildSequence then asks for the same vicinity twice: once for the non-pinned quadrant and once for the trailing pinned quadrant.

The first layout hides the problem, because _needsDelegateRebuild routes both calls through _buildChild. Any later layout without a delegate rebuild — a scroll, for instance — takes the _reuseChild path, and the second request trips the assert in two_dimensional_viewport.dart:

'elementToReuse != null': Expected to re-use an element at (row: 0, column: 2), but none was found.

This is easy to hit in practice: a table with a pinned trailing "actions" or "total" column whose columns happen to fit the viewport crashes on the first scroll.

The fix excludes the trailing pinned spans from the fallback, matching what _updateColumnMetrics / _updateRowMetrics already do when they compute the same value (delegate.columnCount! - delegate.trailingPinnedColumnCount - 1). trailingPinned*Count is required to be 0 when the corresponding count is null, so the subtraction is a no-op for infinite tables.

Two regression tests are added, one for columns and one for rows. Both fail on main with the assert above and pass with the fix.

Fixes flutter/flutter#192152

Pre-Review Checklist

AI disclosure

Per the AI contribution guidelines: this change was written with the assistance of an AI coding agent (Claude Code). I have reviewed the diff, reproduced the crash against main, and verified the tests fail without the fix and pass with it. I take responsibility for the contents of this PR.

flutter test in packages/two_dimensional_scrollables:

  • with the fix: +167: All tests passed!
  • with lib/ reverted to main: the two new tests fail with 'elementToReuse != null': Expected to re-use an element at (row: 0, column: 2), but none was found.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MRZHkuG9nCwvNdMgxU8gQm

Footnotes

  1. Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling. 2

@github-actions github-actions Bot added the p: two_dimensional_scrollables Issues pertaining to the two_dimensional_scrollables package label Sep 2, 2026
@google-cla

google-cla Bot commented Sep 2, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request updates the two_dimensional_scrollables package to version 0.5.5. It fixes a crash occurring when trailing pinned rows or columns fall within the viewport's cache extent by excluding them from the calculation of the last non-pinned column and row indices in RenderTableViewport. This prevents the same columns or rows from being laid out in both the non-pinned and trailing pinned quadrants. Additionally, regression tests have been added to verify this behavior. There are no review comments to address.

@icartsh

icartsh commented Sep 2, 2026

Copy link
Copy Markdown
Author

CLA signed.

…t twice

`RenderTableViewport._updateFirstAndLastVisibleCell` binary searches for
the last non-pinned span whose trailing offset reaches the target
trailing pixel. When the whole table fits inside the viewport plus the
cache extent, that search finds nothing and the code falls back to the
last index of the metrics map. With `trailingPinnedColumnCount` (or
`trailingPinnedRowCount`) greater than zero, that index is a pinned span.

`layoutChildSequence` then requests the same vicinity twice: once for the
non-pinned quadrant and once for the trailing pinned quadrant. The first
layout is hidden because `_needsDelegateRebuild` routes both calls
through `_buildChild`, but any later layout without a delegate rebuild
(a scroll, for example) takes the `_reuseChild` path and trips
`assert(elementToReuse != null)` on the second request:

    'elementToReuse != null': Expected to re-use an element at
    (row: 0, column: 2), but none was found.

Exclude the trailing pinned spans from the fallback, matching what
`_updateColumnMetrics`/`_updateRowMetrics` already do when they compute
the same value.

Fixes flutter/flutter#192152
@icartsh

icartsh commented Sep 2, 2026

Copy link
Copy Markdown
Author

Dropped the co-author trailer so the CLA check only sees the signed author.

@icartsh
icartsh force-pushed the fix-tds-trailing-pinned-last-visible branch from 7ed38b1 to 343a066 Compare September 2, 2026 08:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

p: two_dimensional_scrollables Issues pertaining to the two_dimensional_scrollables package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[two_dimensional_scrollables] TableView asserts 'Expected to re-use an element' on relayout when trailing pinned spans fall within the cache extent

1 participant