Unverified Commit b4953c37 authored by Delwin Mathew's avatar Delwin Mathew Committed by GitHub

Fix null check crash by ReorderableList (#132153)

Fix issue where if you drag the last element of `ReorderableList` and put it in the same index, a `Null check` error arises. This happens as index in `_items` is out of bounds (when `reverse: true`). Fix is to check if last element, dragged element and drop index is same, and return as nothing has changed. Find this video attached.

https://github.com/flutter/flutter/assets/84124091/8043cac3-eb08-42e1-87e7-8095ecab09dc

Fixes issue #132077
parent a80af67a
......@@ -798,6 +798,25 @@ class SliverReorderableListState extends State<SliverReorderableList> with Ticke
}
void _dragEnd(_DragInfo item) {
// No changes required if last child is being inserted into the last position.
if ((_insertIndex! + 1 == _items.length) && _reverse) {
final RenderBox lastItemRenderBox = _items[_items.length - 1]!.context.findRenderObject()! as RenderBox;
final Offset lastItemOffset = lastItemRenderBox.localToGlobal(Offset.zero);
// When drag starts, the corresponding element is removed from
// the list, and moves inside of [ReorderableListState.CustomScrollView],
// which gives [CustomScrollView] a variable height.
//
// So when the element is moved, delta would change accordingly,
// and since it's the last element,
// we animate it back to it's position and add it back to the list.
final double delta = item.itemSize.height;
setState(() {
_finalDropPosition = Offset(lastItemOffset.dx, lastItemOffset.dy - delta);
});
return;
}
setState(() {
if (_insertIndex == item.index) {
_finalDropPosition = _itemOffsetAt(_insertIndex! + (_reverse ? 1 : 0));
......
......@@ -1309,6 +1309,51 @@ void main() {
expect(offsetForFastScroller / offsetForSlowScroller, fastVelocityScalar / slowVelocityScalar);
});
testWidgets('Null check error when dragging and dropping last element into last index with reverse:true', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/132077
const int itemCount = 5;
final List<String> items = List<String>.generate(itemCount, (int index) => 'Item ${index+1}');
await tester.pumpWidget(
MaterialApp(
home: ReorderableList(
onReorder: (int oldIndex, int newIndex) {
if (newIndex > oldIndex) {
newIndex -= 1;
}
final String item = items.removeAt(oldIndex);
items.insert(newIndex, item);
},
itemCount: items.length,
reverse: true,
itemBuilder: (BuildContext context, int index) {
return ReorderableDragStartListener(
key: Key('$index'),
index: index,
child: Material(
child: ListTile(
title: Text(items[index]),
),
),
);
},
),
)
);
// Start gesture on last item
final TestGesture drag = await tester.startGesture(tester.getCenter(find.text('Item 5')));
await tester.pump(kLongPressTimeout);
// Drag to move up the last item, and drop at the last index
await drag.moveBy(const Offset(0, -50));
await tester.pump();
await drag.up();
await tester.pumpAndSettle();
expect(tester.takeException(), null);
});
}
class TestList extends StatelessWidget {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment