2

I am trying the widgets.AnimatedGrid.1 mysample of AnimatedGrid class documentation and I am always getting a RangeError (index): Invalid value: Not in inclusive range whenever I replace at runtime the late ListModel<int> _list in _AnimatedGridSampleState with a new shorter list.

Simply replacing the code of _insert handler with:

void _insert() {
  setState(() {
    _list = ListModel<int>(
      listKey: _gridKey,
      initialItems: <int>[7, 6, 5],
      removedItemBuilder: _buildRemovedItem,
    );
  });
}

then clicking on + button will throw a RangeError.

Since build() in AnimatedGridSampleState depends of _list I was expecting that it will build a new AnimatedGrid with the correct initialItemCount and avoiding RangeError:

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: ...,
        body: Padding(
          padding: const EdgeInsets.all(16.0),
          child: AnimatedGrid(
            key: _gridKey,
            initialItemCount: _list.length,
            itemBuilder: _buildItem,
          ),
        ),
      ),
    );
  }

Yet, the _buildItem(...) it is still being called with the same indexes of the former longer _list. Why?


You can try it by yourself running on the browser in the snippet container of AnimatedGrid page, replacing _insert() code just like shown in the following print screens. You will not see the RangeError but you will see that former items 4, 5, 6 remain on the AnimatedGrid.

Miguel Gamboa
  • 8,855
  • 7
  • 47
  • 94

3 Answers3

0

To remove items takes Duration(milliseconds: 300). So setState try to rebuild the items meanwhile and cause the issue. In order to overcome this issue, I came up with removing one by one and then inserting item, created another two method on the ListModel.

class ListModel<E> {
 .....
  void clear() {
    for (int i = _items.length - 1; i >= 0; i--) {
      removeAt(i);
    }
  }

  void addAll(List<E> item) {
    for (int i = 0; i < item.length; i++) {
      insert(i, item[i]);
    }
  }

Now while you like to reset the item.

  void _insert() async {
    _list.clear();
    /// delay to looks good; kDuration takes to remove item, therefore I am using Future method.
    await Future.delayed(const Duration(milliseconds: 300));
    setState(() {
      _list.addAll(<int>[7, 6, 5]);
    });
  }
Md. Yeasin Sheikh
  • 54,221
  • 7
  • 29
  • 56
  • 1
    Although it is working, your proposal is a workaround and not a solution for reseting the list. Your proposal behaves with animation, removing all items and inserting new items. I would like to avoid animation and begin with a new grid with new items. Nevertheless, I think there is no need of `Future.delayed(...)`. I could use your proposal working with a simple non async `clear()` and without any delay, which can be called on demo `_insert()` as `void _insert() => setState(() { _list.clear(); _list.addAll([7, 6, 5]); });`. IMHO maybe you could fix your answer. – Miguel Gamboa Feb 08 '23 at 10:52
  • Actually I liked the animation while playing with it , feel free to adjust the changes you need. – Md. Yeasin Sheikh Feb 08 '23 at 12:21
  • Nevertheless the Future delay is useless. So it is not true that "_to overcome this issue I came up with waiting to finish_". You do not need `await` either. – Miguel Gamboa Feb 08 '23 at 13:15
  • Yes you are right, I might be too focused on animation – Md. Yeasin Sheikh Feb 08 '23 at 14:22
0

Solved this problem by setting a new GlobalKey<AnimatedGridState> _gridKey on _insert handler of the OP example, which is now:

void _insert() {
    setState(() {
      _gridKey = GlobalKey<AnimatedGridState>();
      _list = ListModel<int>(
        listKey: _gridKey,
        initialItems: <int>[7, 6, 5],
        removedItemBuilder: _buildRemovedItem,
      );
    });
  }
Miguel Gamboa
  • 8,855
  • 7
  • 47
  • 94
  • This is not a good solution. You update the key everytime you have a new data, which causes to lose the state of your widget. Instead of this, use: Single: _gridKey.currentState?.insertItem(index); Multiple: _gridKey.currentState?.insertAllItems(index, newData.length); – Ali Yucel Akgul Apr 07 '23 at 01:07
-1

it would be better if you could share the function responsible for removing the item but i can guess what might be the problem if you are getting this error when removing the last item in the list

if you are removing an item then you need to be careful about something first let's take a look at the removing function

E removeAt(int index) {
final E removedItem = _items.removeAt(index);
if (removedItem != null) {
  _animatedGrid!.removeItem(
    index,
    (BuildContext context, Animation<double> animation) {
      return removedItemBuilder(removedItem, context, animation);
    },
  );
}
return removedItem;

as you can see at the start of the function we are using the index to remove the item we want to remove and storing it in a new variable
then we are using it here

 _animatedGrid!.removeItem(
index,
(BuildContext context, Animation<double> animation) {
  return removedItemBuilder(removedItem, context, animation);
},);

as you can see we are using the item that we removed from the list because it will be displayed during the animation that's why we need the item but not the index and we can't use the index directly in this part cause we already removed the item from the list so if we used it like that

 _animatedGrid!.removeItem(
    index,
    (BuildContext context, Animation<double> animation) {
      return removedItemBuilder(_items[index], context, animation);
    },
  );

you will be getting a RangeError (index): Invalid value: Not in inclusive range because this item is already removed and so it's index is out of range