Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/two_dimensional_scrollables/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.5.5

* Fixes a crash when trailing pinned rows or columns fall within the viewport's cache extent.

## 0.5.4

* Fixes memory leaks.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1087,7 +1087,10 @@ class RenderTableViewport extends RenderTwoDimensionalViewport {
(span) => !span.isPinned && span.trailingOffset >= _targetTrailingColumnPixel,
);
if (_firstNonPinnedColumn != null) {
_lastNonPinnedColumn ??= _columnMetrics.length - 1;
// The last column of the metrics may be a trailing pinned column, which
// is laid out separately. Exclude them so the same column is not laid out
// in both the non-pinned and the trailing pinned quadrants.
_lastNonPinnedColumn ??= _columnMetrics.length - 1 - delegate.trailingPinnedColumnCount;
}

if (_rowMetrics.isNotEmpty) {
Expand Down Expand Up @@ -1116,7 +1119,10 @@ class RenderTableViewport extends RenderTwoDimensionalViewport {
(span) => !span.isPinned && span.trailingOffset >= _targetTrailingRowPixel,
);
if (_firstNonPinnedRow != null) {
_lastNonPinnedRow ??= _rowMetrics.length - 1;
// The last row of the metrics may be a trailing pinned row, which is laid
// out separately. Exclude them so the same row is not laid out in both
// the non-pinned and the trailing pinned quadrants.
_lastNonPinnedRow ??= _rowMetrics.length - 1 - delegate.trailingPinnedRowCount;
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/two_dimensional_scrollables/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: two_dimensional_scrollables
description: Widgets that scroll using the two dimensional scrolling foundation.
version: 0.5.4
version: 0.5.5
repository: https://github.com/flutter/packages/tree/main/packages/two_dimensional_scrollables
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+two_dimensional_scrollables%22+

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4085,6 +4085,92 @@ void main() {
expect(mergedRect.top, 200);
expect(mergedRect.bottom, 400);
});

testWidgets('Trailing pinned columns are not laid out twice when the table fits the viewport', (
WidgetTester tester,
) async {
// Regression test for https://github.com/flutter/flutter/issues/192152
final verticalController = ScrollController();
addTearDown(verticalController.dispose);

await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: SizedBox(
height: 300,
width: 400,
// All of the columns fit within the viewport, so the search for the
// last visible non-pinned column comes up empty and falls back to
// the last column of the metrics - which is a trailing pinned one.
child: TableView.builder(
verticalDetails: ScrollableDetails.vertical(controller: verticalController),
columnCount: 3,
rowCount: 100,
pinnedRowCount: 1,
trailingPinnedColumnCount: 1,
columnBuilder: (int index) => const TableSpan(extent: FixedTableSpanExtent(100)),
rowBuilder: (int index) => const TableSpan(extent: FixedTableSpanExtent(36)),
cellBuilder: (BuildContext context, TableVicinity vicinity) {
return TableViewCell(child: Text('R${vicinity.row} C${vicinity.column}'));
},
),
),
),
),
);
expect(find.text('R0 C2'), findsOneWidget);

// Lay out again without rebuilding the delegate, which exercises the child
// reuse path. The trailing pinned column must not be requested twice.
verticalController.jumpTo(1);
await tester.pump();

expect(tester.takeException(), isNull);
expect(find.text('R0 C2'), findsOneWidget);
});

testWidgets('Trailing pinned rows are not laid out twice when the table fits the viewport', (
WidgetTester tester,
) async {
// Regression test for https://github.com/flutter/flutter/issues/192152
final horizontalController = ScrollController();
addTearDown(horizontalController.dispose);

await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: SizedBox(
height: 400,
width: 300,
// All of the rows fit within the viewport, so the search for the
// last visible non-pinned row comes up empty and falls back to the
// last row of the metrics - which is a trailing pinned one.
child: TableView.builder(
horizontalDetails: ScrollableDetails.horizontal(controller: horizontalController),
columnCount: 100,
rowCount: 3,
pinnedColumnCount: 1,
trailingPinnedRowCount: 1,
columnBuilder: (int index) => const TableSpan(extent: FixedTableSpanExtent(36)),
rowBuilder: (int index) => const TableSpan(extent: FixedTableSpanExtent(100)),
cellBuilder: (BuildContext context, TableVicinity vicinity) {
return TableViewCell(child: Text('R${vicinity.row} C${vicinity.column}'));
},
),
),
),
),
);
expect(find.text('R2 C0'), findsOneWidget);

// Lay out again without rebuilding the delegate, which exercises the child
// reuse path. The trailing pinned row must not be requested twice.
horizontalController.jumpTo(1);
await tester.pump();

expect(tester.takeException(), isNull);
expect(find.text('R2 C0'), findsOneWidget);
});
}

class _NullBuildContext implements BuildContext, TwoDimensionalChildManager {
Expand Down