Fixed extent lists cannot handle infinite extent (#8885)

Previously they tried to compute an integer target end index, but
integers can't represent infinity. Now we use null to represent
infinity.

Also, fix some similar issues with grids.

Fixes #8398
diff --git a/packages/flutter/lib/src/rendering/sliver_fixed_extent_list.dart b/packages/flutter/lib/src/rendering/sliver_fixed_extent_list.dart
index 26c5dae..fd51294 100644
--- a/packages/flutter/lib/src/rendering/sliver_fixed_extent_list.dart
+++ b/packages/flutter/lib/src/rendering/sliver_fixed_extent_list.dart
@@ -120,13 +120,14 @@
     );
 
     final int firstIndex = getMinChildIndexForScrollOffset(scrollOffset, itemExtent);
-    final int targetLastIndex = getMaxChildIndexForScrollOffset(targetEndScrollOffset, itemExtent);
+    final int targetLastIndex = targetEndScrollOffset.isFinite ?
+        getMaxChildIndexForScrollOffset(targetEndScrollOffset, itemExtent) : null;
 
     if (firstChild != null) {
       final int oldFirstIndex = indexOf(firstChild);
       final int oldLastIndex = indexOf(lastChild);
       final int leadingGarbage = (firstIndex - oldFirstIndex).clamp(0, childCount);
-      final int trailingGarbage = (oldLastIndex - targetLastIndex).clamp(0, childCount);
+      final int trailingGarbage = targetLastIndex == null ? 0 : (oldLastIndex - targetLastIndex).clamp(0, childCount);
       if (leadingGarbage + trailingGarbage > 0)
         collectGarbage(leadingGarbage, trailingGarbage);
     }
@@ -156,7 +157,7 @@
       trailingChildWithLayout = firstChild;
     }
 
-    while (indexOf(trailingChildWithLayout) < targetLastIndex) {
+    while (targetLastIndex == null || indexOf(trailingChildWithLayout) < targetLastIndex) {
       RenderBox child = childAfter(trailingChildWithLayout);
       if (child == null) {
         child = insertAndLayoutChild(childConstraints, after: trailingChildWithLayout);
@@ -180,7 +181,7 @@
     assert(firstIndex == 0 || childScrollOffset(firstChild) <= scrollOffset);
     assert(debugAssertChildListIsNonEmptyAndContiguous());
     assert(indexOf(firstChild) == firstIndex);
-    assert(lastIndex <= targetLastIndex);
+    assert(targetLastIndex == null || lastIndex <= targetLastIndex);
 
     final double estimatedMaxScrollOffset = estimateMaxScrollOffset(
       constraints,
@@ -201,7 +202,8 @@
       paintExtent: paintExtent,
       maxPaintExtent: estimatedMaxScrollOffset,
       // Conservative to avoid flickering away the clip during scroll.
-      hasVisualOverflow: lastIndex >= targetLastIndex || constraints.scrollOffset > 0.0,
+      hasVisualOverflow: (targetLastIndex != null && lastIndex >= targetLastIndex)
+        || constraints.scrollOffset > 0.0,
     );
 
     assert(childManager.debugAssertChildListLocked());
diff --git a/packages/flutter/lib/src/rendering/sliver_grid.dart b/packages/flutter/lib/src/rendering/sliver_grid.dart
index 484bbf0..dec6010 100644
--- a/packages/flutter/lib/src/rendering/sliver_grid.dart
+++ b/packages/flutter/lib/src/rendering/sliver_grid.dart
@@ -172,13 +172,16 @@
 
   @override
   int getMinChildIndexForScrollOffset(double scrollOffset) {
-    return crossAxisCount * (scrollOffset ~/ mainAxisStride);
+    return mainAxisStride > 0.0 ? crossAxisCount * (scrollOffset ~/ mainAxisStride) : 0;
   }
 
   @override
   int getMaxChildIndexForScrollOffset(double scrollOffset) {
-    final int mainAxisCount = (scrollOffset / mainAxisStride).ceil();
-    return math.max(0, crossAxisCount * mainAxisCount - 1);
+    if (mainAxisStride > 0.0) {
+      final int mainAxisCount = (scrollOffset / mainAxisStride).ceil();
+      return math.max(0, crossAxisCount * mainAxisCount - 1);
+    }
+    return 0;
   }
 
   @override
@@ -486,13 +489,14 @@
     final SliverGridLayout layout = _gridDelegate.getLayout(constraints);
 
     final int firstIndex = layout.getMinChildIndexForScrollOffset(scrollOffset);
-    final int targetLastIndex = layout.getMaxChildIndexForScrollOffset(targetEndScrollOffset);
+    final int targetLastIndex = targetEndScrollOffset.isFinite ?
+      layout.getMaxChildIndexForScrollOffset(targetEndScrollOffset) : null;
 
     if (firstChild != null) {
       final int oldFirstIndex = indexOf(firstChild);
       final int oldLastIndex = indexOf(lastChild);
       final int leadingGarbage = (firstIndex - oldFirstIndex).clamp(0, childCount);
-      final int trailingGarbage = (oldLastIndex - targetLastIndex).clamp(0, childCount);
+      final int trailingGarbage = targetLastIndex == null ? 0 : (oldLastIndex - targetLastIndex).clamp(0, childCount);
       if (leadingGarbage + trailingGarbage > 0)
         collectGarbage(leadingGarbage, trailingGarbage);
     }
@@ -532,7 +536,7 @@
       trailingChildWithLayout = firstChild;
     }
 
-    for (int index = indexOf(trailingChildWithLayout) + 1; index <= targetLastIndex; ++index) {
+    for (int index = indexOf(trailingChildWithLayout) + 1; targetLastIndex == null || index <= targetLastIndex; ++index) {
       final SliverGridGeometry gridGeometry = layout.getGeometryForChildIndex(index);
       final BoxConstraints childConstraints = gridGeometry.getBoxConstraints(constraints);
       RenderBox child = childAfter(trailingChildWithLayout);
@@ -559,7 +563,7 @@
     assert(childScrollOffset(firstChild) <= scrollOffset);
     assert(debugAssertChildListIsNonEmptyAndContiguous());
     assert(indexOf(firstChild) == firstIndex);
-    assert(lastIndex <= targetLastIndex);
+    assert(targetLastIndex == null || lastIndex <= targetLastIndex);
 
     final double estimatedTotalExtent = childManager.estimateMaxScrollOffset(
       constraints,
diff --git a/packages/flutter/test/widgets/grid_view_test.dart b/packages/flutter/test/widgets/grid_view_test.dart
index e81dfae..10ccabf 100644
--- a/packages/flutter/test/widgets/grid_view_test.dart
+++ b/packages/flutter/test/widgets/grid_view_test.dart
@@ -328,6 +328,47 @@
     expect(find.byType(GridView), isNot(paints..rect(color: green)..rect(color: green)..rect(color: green)));
   });
 
+  testWidgets('GridView in zero context', (WidgetTester tester) async {
+    await tester.pumpWidget(
+      new Center(
+        child: new SizedBox(
+          width: 0.0,
+          height: 0.0,
+          child: new GridView.count(
+            crossAxisCount: 4,
+            children: new List<Widget>.generate(20, (int i) {
+              return new Container(
+                child: new Text('$i'),
+              );
+            }),
+          ),
+        ),
+      ),
+    );
+
+    expect(find.text('0'), findsOneWidget);
+    expect(find.text('1'), findsNothing);
+  });
+
+  testWidgets('GridView in unbounded context', (WidgetTester tester) async {
+    await tester.pumpWidget(
+      new SingleChildScrollView(
+        child: new GridView.count(
+          crossAxisCount: 4,
+          shrinkWrap: true,
+          children: new List<Widget>.generate(20, (int i) {
+            return new Container(
+              child: new Text('$i'),
+            );
+          }),
+        ),
+      ),
+    );
+
+    expect(find.text('0'), findsOneWidget);
+    expect(find.text('19'), findsOneWidget);
+  });
+
   // TODO(ianh): can you tap a grid cell that is slightly off the bottom of the screen?
   // (try it with the flutter_gallery Grid demo)
 }
diff --git a/packages/flutter/test/widgets/list_view_test.dart b/packages/flutter/test/widgets/list_view_test.dart
index e4a6b59..74b498d 100644
--- a/packages/flutter/test/widgets/list_view_test.dart
+++ b/packages/flutter/test/widgets/list_view_test.dart
@@ -144,4 +144,23 @@
     expect(find.text('4'), findsOneWidget);
     expect(find.text('5'), findsNothing);
   });
+
+  testWidgets('ListView with itemExtent in unbounded context', (WidgetTester tester) async {
+    await tester.pumpWidget(
+      new SingleChildScrollView(
+        child: new ListView(
+          itemExtent: 100.0,
+          shrinkWrap: true,
+          children: new List<Widget>.generate(20, (int i) {
+            return new Container(
+              child: new Text('$i'),
+            );
+          }),
+        ),
+      ),
+    );
+
+    expect(find.text('0'), findsOneWidget);
+    expect(find.text('19'), findsOneWidget);
+  });
 }