'Flutter: After deleting the last item in ListView item, it always gives a RangeError. Why doesn't it stop on new itemCount.length?

I've a listview of products and when I delete any item other than the last item, it works fine. But when I delete the last item in the listview, may it be 0th or 5th, it always gives this error.

main.dart

routes: {
        '/': (BuildContext context) => ProductsPage(_products, _addProduct, _deleteProduct),
        '/admin': (BuildContext context) => ProductsAdminPage(),
      },
onGenerateRoute: (RouteSettings settings) {
        final List<String> pathElements = settings.name.split('/');
        if (pathElements[0] != '') {
          return null;
        }
        if (pathElements[1] == 'product') {
          final int index = int.parse(pathElements[2]);
          print('[main.dart:] current items lefts: ' + _products.length.toString());
          print('[main.dart:] current index: ' + index.toString());
          return MaterialPageRoute<bool>(
            builder: (BuildContext context) {
              print('checking error place.');
              print(index);
/* === PRECISE LOCATION OF ERROR WHEN THERE IS NO ITEM IS LEFT AND LAST ONE IS DELETED === */
/*
I/flutter ( 7709): [main.dart:] current items lefts: 1
I/flutter ( 7709): [main.dart:] current index: 0
I/flutter ( 7709): checking error place.
I/flutter ( 7709): 0
I/flutter ( 7709): Sweets
I/flutter ( 7709): assets/food.jpg
I/flutter ( 7709): [Products Widget] Product removedAt index: 0
I/flutter ( 7709): checking error place.
I/flutter ( 7709): 0

Another exception was thrown: RangeError (index): Invalid value: Valid value range is empty: 0
I/flutter ( 7709): [ProductManager State] build()
I/flutter ( 7709): [Products Widget] Constructor

════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The following RangeError was thrown building Builder(dirty):
RangeError (index): Invalid value: Valid value range is empty: 0

The relevant error-causing widget was: 
  MaterialApp file:///D:/MobileDev/maximillian/tutorial/lib/main.dart:48:12
When the exception was thrown, this was the stack: 
#0      List.[] (dart:core-patch/growable_array.dart:166:60)
#1      _MyAppState.build.<anonymous closure>.<anonymous closure> (package:tutorial/main.dart:72:30)
#2      MaterialRouteTransitionMixin.buildPage (package:flutter/src/material/page.dart:104:34)
#3      _ModalScopeState.build.<anonymous closure> (package:flutter/src/widgets/routes.dart:826:45)
#4      Builder.build (package:flutter/src/widgets/basic.dart:7118:48)
...
════════════════════════════════════════════════════════════════════════════════════════════════════
I/flutter ( 7709): [Products Widget] build()

*/
              print(_products[index]['title']);
              print(_products[index]['image']);
              return ProductPage(_products[index]['title'], _products[index]['image']);
            },
          );
        }
        return null;
      },

ProductPage

class ProductPage extends StatelessWidget {
  final String title;
  final String imageUrl;

  ProductPage(this.title, this.imageUrl);

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () {
        print('Back button pressed');
        Navigator.of(context).pop(false);
        return Future.value(false); // only to accommodate the return type WillPopScope widget
      },
      child: Scaffold(
        appBar: AppBar(
          title: Text(title),
        ),
        body: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Image.asset(imageUrl),
            Container(padding: EdgeInsets.all(10.0), child: Text(title)),
            Container(
              padding: EdgeInsets.all(10.0),
              child: RaisedButton(
                color: Theme.of(context).accentColor,
                onPressed: () => Navigator.of(context).pop(true),
                child: Text('Delete'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Products widget

class Products extends StatelessWidget {
  final List<Map<String, String>> products;
  final Function deleteProduct;

  Products(this.products, {this.deleteProduct}) {
    print('[Products Widget] Constructor');
  }

  Widget _buildProductList() {
    Widget productCards;
    if (products.length > 0) {
/* === PRECISE LOCATION OF ERROR WHEN THE LAST ONE OF TWO ITEMS IS DELETED, ONLY ONE ITEM LEFT IN THE LISTVIEW
VIEW THE ERROR BELOW TO SEE BELOW PRINTED LINE. ===

I/flutter ( 7709): products.dart: total items - 1
 */
      print('products.dart: total items - ' + products.length.toString()); // 1
      productCards = ListView.builder(
        // builder always passes two args implicitly. context & index
        itemBuilder: _buildProductItem,
        itemCount: products.length,
      );
    } else {
      productCards = Center(
        child: Text('No Products found. Please add some'),
      );
    }

    return productCards;
  }


  Widget _buildProductItem(BuildContext context, int index) {
/* === WHERE THE LAST ONE OF TWO ITEMS IS DELETED, ONLY ONE ITEM LEFT IN THE LISTVIEW
THE BELOW LINE PRINTS 0 BUT IT GIVES THE FOLLOWING ERROR BEFORE THIS. */
/*
I/flutter ( 7709): products.dart: total items - 1

════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The following RangeError was thrown building Builder(dirty):
RangeError (index): Invalid value: Only valid value is 0: 1

The relevant error-causing widget was: 
  MaterialApp file:///D:/MobileDev/maximillian/tutorial/lib/main.dart:48:12
When the exception was thrown, this was the stack: 
#0      List.[] (dart:core-patch/growable_array.dart:166:60)
#1      _MyAppState.build.<anonymous closure>.<anonymous closure> (package:tutorial/main.dart:72:30)
#2      MaterialRouteTransitionMixin.buildPage (package:flutter/src/material/page.dart:104:34)
#3      _ModalScopeState.build.<anonymous closure> (package:flutter/src/widgets/routes.dart:826:45)
#4      Builder.build (package:flutter/src/widgets/basic.dart:7118:48)
...
════════════════════════════════════════════════════════════════════════════════════════════════════
I/flutter ( 7709): [product widget] current item index: 0

*/
    print('[product widget] current item index: ' + index.toString()); // 0
    return Card(
      child: Column(
        children: <Widget>[
          Image.asset(products[index]['image']),
          Text(products[index]['title']),
          ButtonBar(
            alignment: MainAxisAlignment.center,
            children: [
              FlatButton(
                child: Text('Details'),
                onPressed: () => Navigator.pushNamed<bool>(
                        context, '/product/' + index.toString())
                    .then((bool value) {
                  if (value) {
                    deleteProduct(index);
                  }
                }),
              ),
            ],
          )
        ],
      ),
    );
  }

  Widget _buildProductItem(BuildContext context, int index) {
/* === WHERE THE LAST ONE OF TWO ITEMS IS DELETED, ONLY ONE ITEM LEFT IN THE LISTVIEW
THE BELOW LINE PRINTS 0 BUT IT GIVES THE FOLLOWING ERROR BEFORE THIS. */
/*
I/flutter ( 7709): products.dart: total items - 1

════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The following RangeError was thrown building Builder(dirty):
RangeError (index): Invalid value: Only valid value is 0: 1

The relevant error-causing widget was: 
  MaterialApp file:///D:/MobileDev/maximillian/tutorial/lib/main.dart:48:12
When the exception was thrown, this was the stack: 
#0      List.[] (dart:core-patch/growable_array.dart:166:60)
#1      _MyAppState.build.<anonymous closure>.<anonymous closure> (package:tutorial/main.dart:72:30)
#2      MaterialRouteTransitionMixin.buildPage (package:flutter/src/material/page.dart:104:34)
#3      _ModalScopeState.build.<anonymous closure> (package:flutter/src/widgets/routes.dart:826:45)
#4      Builder.build (package:flutter/src/widgets/basic.dart:7118:48)
...
════════════════════════════════════════════════════════════════════════════════════════════════════
I/flutter ( 7709): [product widget] current item index: 0

*/
    print('[product widget] current item index: ' + index.toString()); // 0
    return Card(
      child: Column(
        children: <Widget>[
          Image.asset(products[index]['image']),
          Text(products[index]['title']),
          ButtonBar(
            alignment: MainAxisAlignment.center,
            children: [
              FlatButton(
                child: Text('Details'),
                onPressed: () => Navigator.pushNamed<bool>(
                        context, '/product/' + index.toString())
                    .then((bool value) {
                  if (value) {
                    deleteProduct(index);
                  }
                }),
              ),
            ],
          )
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    print('[Products Widget] build()');
    return _buildProductList();
  }
}

In either case, the required page loads fine after deletion, however, there's a blink of red error screen just before the required page is loaded.



Solution 1:[1]

It's a bit late I had a similar problem and I solved it by using the condition around the widget page and not inside it something like

if(_products.isNotEmpty){
   return ProductPage(_products[index]['title'], _products[index]['image']);
} else {
   return Center(
        child: Text('No Products found. Please add some'),
      );
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1