Search code examples
fluttercheckboxreorderable-list

Flutter Checkbox wrong animation inside a ReorderableListView


I'm must doing something wrong, but when I reorder a checked checkbox inside a ReorderableListView, it is animating the unchecked tile:

enter image description here

Here a sample code that I'm using:

ReorderableListView(
  padding: const EdgeInsets.symmetric(horizontal: 40),
  children: <Widget>[
    for (int index = 0; index < _items.length; index++)
      ListTile(
        leading: Checkbox(
          
          key: Key('$index'),
          onChanged: (v) => null,
          value: _items[index].isOdd ? true : false,
        ),
        key: Key('$index'),
        tileColor: _items[index].isOdd ? oddItemColor : evenItemColor,
        title: Text('Item ${_items[index]}'),
      ),
  ],
  onReorder: (int oldIndex, int newIndex) {
    setState(() {
      if (oldIndex < newIndex) {
        newIndex -= 1;
      }
      final int item = _items.removeAt(oldIndex);
      _items.insert(newIndex, item);
    });

Full code here!

How can I do it properly? Cheers!


Solution

  • First off, you only need a key on ListTile, as they are the direct descendants of the ReorderableListView.

    Second, the key needs to non-index-based. You can use UniqueKey or GlobalKey, but the best practice is to use a ValueKey, passing the value at that index.

    In your case:

    class _MyStatefulWidgetState extends State<MyStatefulWidget> {
      final List<int> _items = List<int>.generate(2, (int index) => index);
    
      @override
      Widget build(BuildContext context) {
        return ReorderableListView(
          padding: const EdgeInsets.symmetric(horizontal: 40),
          children: <Widget>[
            for (int index = 0; index < _items.length; index++)
              ListTile(
                leading: Checkbox(
                  onChanged: (v) => null,
                  value: _items[index].isOdd ? true : false,
                ),
                // here
                key: ValueKey(_items[index]),
                tileColor: _items[index].isOdd ? oddItemColor : evenItemColor,
                title: Text('Item ${_items[index]}'),
              ),
          ],
          onReorder: (int oldIndex, int newIndex) {
            setState(() {
              if (oldIndex < newIndex) {
                newIndex -= 1;
              }
              final int item = _items.removeAt(oldIndex);
              _items.insert(newIndex, item);
            });
          },
        );
      }
    }
    

    As for why your indexed keys don't work - my understanding is that after the widget rebuilds (from reordering the list), your ListTile is keyed at the wrong index (because the index of that ListTile has changed). When you use the value, that is a constant that can be used to identify the widget.

    Emily Fortuna on Medium has written a wonderful article on keys that I would highly recommend reading if you are going to continue developing with Flutter.