Skip to content

Commit 6707f5e

Browse files
authored
Change ItemExtentBuilder's return value nullable (#142428)
Fixes flutter/flutter#138912 Change `ItemExtentBuilder`'s return value nullable, it should return null if asked to build an item extent with a greater index than exists.
1 parent bb1271d commit 6707f5e

File tree

7 files changed

+80
-14
lines changed

7 files changed

+80
-14
lines changed

packages/flutter/lib/src/rendering/sliver.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@ import 'viewport_offset.dart';
1818

1919
/// Called to get the item extent by the index of item.
2020
///
21+
/// Should return null if asked to build an item extent with a greater index than
22+
/// exists.
23+
///
2124
/// Used by [ListView.itemExtentBuilder] and [SliverVariedExtentList.itemExtentBuilder].
22-
typedef ItemExtentBuilder = double Function(int index, SliverLayoutDimensions dimensions);
25+
typedef ItemExtentBuilder = double? Function(int index, SliverLayoutDimensions dimensions);
2326

2427
/// Relates the dimensions of the [RenderSliver] during layout.
2528
///

packages/flutter/lib/src/rendering/sliver_fixed_extent_list.dart

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,17 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
6767
return itemExtent * index;
6868
} else {
6969
double offset = 0.0;
70+
double? itemExtent;
7071
for (int i = 0; i < index; i++) {
71-
offset += itemExtentBuilder!(i, _currentLayoutDimensions);
72+
final int? childCount = childManager.estimatedChildCount;
73+
if (childCount != null && i > childCount - 1) {
74+
break;
75+
}
76+
itemExtent = itemExtentBuilder!(i, _currentLayoutDimensions);
77+
if (itemExtent == null) {
78+
break;
79+
}
80+
offset += itemExtent;
7281
}
7382
return offset;
7483
}
@@ -179,8 +188,13 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
179188
return childManager.childCount * itemExtent;
180189
} else {
181190
double offset = 0.0;
191+
double? itemExtent;
182192
for (int i = 0; i < childManager.childCount; i++) {
183-
offset += itemExtentBuilder!(i, _currentLayoutDimensions);
193+
itemExtent = itemExtentBuilder!(i, _currentLayoutDimensions);
194+
if (itemExtent == null) {
195+
break;
196+
}
197+
offset += itemExtent;
184198
}
185199
return offset;
186200
}
@@ -212,8 +226,17 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
212226
}
213227
double position = 0.0;
214228
int index = 0;
229+
double? itemExtent;
215230
while (position < scrollOffset) {
216-
position += callback(index, _currentLayoutDimensions);
231+
final int? childCount = childManager.estimatedChildCount;
232+
if (childCount != null && index > childCount - 1) {
233+
break;
234+
}
235+
itemExtent = callback(index, _currentLayoutDimensions);
236+
if (itemExtent == null) {
237+
break;
238+
}
239+
position += itemExtent;
217240
++index;
218241
}
219242
return index - 1;
@@ -224,7 +247,7 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
224247
if (itemExtentBuilder == null) {
225248
extent = itemExtent!;
226249
} else {
227-
extent = itemExtentBuilder!(index, _currentLayoutDimensions);
250+
extent = itemExtentBuilder!(index, _currentLayoutDimensions)!;
228251
}
229252
return constraints.asBoxConstraints(
230253
minExtent: extent,

packages/flutter/lib/src/rendering/sliver_multi_box_adaptor.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,17 @@ abstract class RenderSliverBoxChildManager {
7979
/// list).
8080
int get childCount;
8181

82+
/// The best available estimate of [childCount], or null if no estimate is available.
83+
///
84+
/// This differs from [childCount] in that [childCount] never returns null (and must
85+
/// not be accessed if the child count is not yet available, meaning the [createChild]
86+
/// method has not been provided an index that does not create a child).
87+
///
88+
/// See also:
89+
///
90+
/// * [SliverChildDelegate.estimatedChildCount], to which this getter defers.
91+
int? get estimatedChildCount => null;
92+
8293
/// Called during [RenderSliverMultiBoxAdaptor.adoptChild] or
8394
/// [RenderSliverMultiBoxAdaptor.move].
8495
///

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1506,6 +1506,9 @@ class ListView extends BoxScrollView {
15061506
/// This will be called multiple times during the layout phase of a frame to find
15071507
/// the items that should be loaded by the lazy loading process.
15081508
///
1509+
/// Should return null if asked to build an item extent with a greater index than
1510+
/// exists.
1511+
///
15091512
/// Unlike [itemExtent] or [prototypeItem], this allows children to have
15101513
/// different extents.
15111514
///

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

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -937,15 +937,7 @@ class SliverMultiBoxAdaptorElement extends RenderObjectElement implements Render
937937
);
938938
}
939939

940-
/// The best available estimate of [childCount], or null if no estimate is available.
941-
///
942-
/// This differs from [childCount] in that [childCount] never returns null (and must
943-
/// not be accessed if the child count is not yet available, meaning the [createChild]
944-
/// method has not been provided an index that does not create a child).
945-
///
946-
/// See also:
947-
///
948-
/// * [SliverChildDelegate.estimatedChildCount], to which this getter defers.
940+
@override
949941
int? get estimatedChildCount => (widget as SliverMultiBoxAdaptorWidget).delegate.estimatedChildCount;
950942

951943
@override

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ class SliverVariedExtentList extends SliverMultiBoxAdaptorWidget {
9898
));
9999

100100
/// The children extent builder.
101+
///
102+
/// Should return null if asked to build an item extent with a greater index than
103+
/// exists.
101104
final ItemExtentBuilder itemExtentBuilder;
102105

103106
@override

packages/flutter/test/widgets/list_view_test.dart

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,37 @@ void main() {
780780
expect(renderObject.clipBehavior, equals(Clip.antiAlias));
781781
});
782782

783+
// Regression test for https://github.com/flutter/flutter/pull/138912
784+
testWidgets('itemExtentBuilder should respect item count', (WidgetTester tester) async {
785+
final ScrollController controller = ScrollController();
786+
addTearDown(controller.dispose);
787+
final List<double> numbers = <double>[
788+
10, 20, 30, 40, 50,
789+
];
790+
await tester.pumpWidget(
791+
Directionality(
792+
textDirection: TextDirection.ltr,
793+
child: ListView.builder(
794+
controller: controller,
795+
itemCount: numbers.length,
796+
itemExtentBuilder: (int index, SliverLayoutDimensions dimensions) {
797+
return numbers[index];
798+
},
799+
itemBuilder: (BuildContext context, int index) {
800+
return SizedBox(
801+
height: numbers[index],
802+
child: Text('Item $index'),
803+
);
804+
},
805+
),
806+
),
807+
);
808+
809+
expect(find.text('Item 0'), findsOneWidget);
810+
expect(find.text('Item 4'), findsOneWidget);
811+
expect(find.text('Item 5'), findsNothing);
812+
});
813+
783814
// Regression test for https://github.com/flutter/flutter/pull/131393
784815
testWidgets('itemExtentBuilder test', (WidgetTester tester) async {
785816
final ScrollController controller = ScrollController();

0 commit comments

Comments
 (0)