Skip to content

Commit b4953c3

Browse files
authored
Fix null check crash by ReorderableList (flutter#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 flutter#132077
1 parent a80af67 commit b4953c3

File tree

2 files changed

+64
-0
lines changed

2 files changed

+64
-0
lines changed

packages/flutter/lib/src/widgets/reorderable_list.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,25 @@ class SliverReorderableListState extends State<SliverReorderableList> with Ticke
798798
}
799799

800800
void _dragEnd(_DragInfo item) {
801+
// No changes required if last child is being inserted into the last position.
802+
if ((_insertIndex! + 1 == _items.length) && _reverse) {
803+
final RenderBox lastItemRenderBox = _items[_items.length - 1]!.context.findRenderObject()! as RenderBox;
804+
final Offset lastItemOffset = lastItemRenderBox.localToGlobal(Offset.zero);
805+
806+
// When drag starts, the corresponding element is removed from
807+
// the list, and moves inside of [ReorderableListState.CustomScrollView],
808+
// which gives [CustomScrollView] a variable height.
809+
//
810+
// So when the element is moved, delta would change accordingly,
811+
// and since it's the last element,
812+
// we animate it back to it's position and add it back to the list.
813+
final double delta = item.itemSize.height;
814+
815+
setState(() {
816+
_finalDropPosition = Offset(lastItemOffset.dx, lastItemOffset.dy - delta);
817+
});
818+
return;
819+
}
801820
setState(() {
802821
if (_insertIndex == item.index) {
803822
_finalDropPosition = _itemOffsetAt(_insertIndex! + (_reverse ? 1 : 0));

packages/flutter/test/widgets/reorderable_list_test.dart

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1309,6 +1309,51 @@ void main() {
13091309

13101310
expect(offsetForFastScroller / offsetForSlowScroller, fastVelocityScalar / slowVelocityScalar);
13111311
});
1312+
1313+
testWidgets('Null check error when dragging and dropping last element into last index with reverse:true', (WidgetTester tester) async {
1314+
// Regression test for https://github.com/flutter/flutter/issues/132077
1315+
const int itemCount = 5;
1316+
final List<String> items = List<String>.generate(itemCount, (int index) => 'Item ${index+1}');
1317+
1318+
await tester.pumpWidget(
1319+
MaterialApp(
1320+
home: ReorderableList(
1321+
onReorder: (int oldIndex, int newIndex) {
1322+
if (newIndex > oldIndex) {
1323+
newIndex -= 1;
1324+
}
1325+
final String item = items.removeAt(oldIndex);
1326+
items.insert(newIndex, item);
1327+
},
1328+
itemCount: items.length,
1329+
reverse: true,
1330+
itemBuilder: (BuildContext context, int index) {
1331+
return ReorderableDragStartListener(
1332+
key: Key('$index'),
1333+
index: index,
1334+
child: Material(
1335+
child: ListTile(
1336+
title: Text(items[index]),
1337+
),
1338+
),
1339+
);
1340+
},
1341+
),
1342+
)
1343+
);
1344+
1345+
// Start gesture on last item
1346+
final TestGesture drag = await tester.startGesture(tester.getCenter(find.text('Item 5')));
1347+
await tester.pump(kLongPressTimeout);
1348+
1349+
// Drag to move up the last item, and drop at the last index
1350+
await drag.moveBy(const Offset(0, -50));
1351+
await tester.pump();
1352+
await drag.up();
1353+
await tester.pumpAndSettle();
1354+
1355+
expect(tester.takeException(), null);
1356+
});
13121357
}
13131358

13141359
class TestList extends StatelessWidget {

0 commit comments

Comments
 (0)