Flutter Checkbox wrong animation inside a ReorderableListView

612 views Asked by At

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!

2

There are 2 answers

2
Alex Hartford On BEST ANSWER

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.

0
Jonas Cerqueira On

I've just realised my mistake, using key: Key('$index') is wrong because it is changing onReorder, so flutter is matching my old checked Checkbox value with the new unchecked Checkbox(My bad =|). Just use an ObjectKey in the ListTile and it will match correctly!

ListTile(
        leading: Checkbox(
          onChanged: (v) => null,
          value: _items[index].isOdd ? true : false,
        ),
        key: ObjectKey(_items[index]),
        tileColor: _items[index].isOdd ? oddItemColor : evenItemColor,
        title: Text('Item ${_items[index]}'),
      ),