3

I am trying to use the findChildIndexCallback to keep the state of my StatefulWidgets inside a ListView.builder.

Can someone please provide an example of how can i actually "find" the Listview children ?

My question directly comes from an issue i have made and that was actually fixed by the Flutter team.

Here is my code so far that is not working :

ListView.builder(
            itemCount: model.messagesList.length,
            findChildIndexCallback: (key) {
              // final ValueKey<String> valueKey = key as ValueKey<String>;
              // return model.messagesList.indexWhere((element) => element.uid == valueKey.value); // Doesn't change anything, my widgets are all rebuilt on insert
            },
            itemBuilder: (context, index) {
              DMessage message = model.messagesList[index];
              return MessageItem(
                key: ValueKey<String>(message.uid)
                message: message
              );
            }),
Tom3652
  • 2,540
  • 3
  • 19
  • 45
  • Refer _documentation_ [here](https://api.flutter.dev/flutter/widgets/SliverChildBuilderDelegate/findChildIndexCallback.html) and refer [this](https://stackoverflow.com/a/65448442/13997210) answer – Ravindra S. Patil May 31 '22 at 13:19
  • thanks for the comment. However i have already read the documentation and i know how this works, but the answer you provided has a code error (in Flutter 3.0.0) already and mine is the same + more up-to-date and still not working... – Tom3652 May 31 '22 at 13:21

1 Answers1

3

Just use StatefulWidget along with AutomaticKeepAliveClientMixin within your item widget. Subclasses must implement wantKeepAlive, and their build methods must call super.build(BuildContext).

class ItemWidget extends StatefulWidget {
  const ItemWidget({Key? key, required this.item}) : super(key: key);

  final int item;

  @override
  State<ItemWidget> createState() => _ItemWidgetState();
}

class _ItemWidgetState extends State<ItemWidget>
    with AutomaticKeepAliveClientMixin {
  bool selected = false;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return ListTile(
      selected: selected,
      title: Text('Index-${widget.item}'),
      onTap: () {
        setState(() {
          selected = !selected;
        });
      },
    );
  }

  @override
  bool get wantKeepAlive => true;
}

And provide the findChildIndexCallback property on your ListView like below :

class ListWidget extends StatelessWidget {
  const ListWidget({Key? key, required this.items}) : super(key: key);

  final List<int> items;

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemBuilder: (context, index) {
        final item = items[index];
        return ItemWidget(key: ValueKey(item), item: item);
      },
      itemCount: items.length,
      findChildIndexCallback: (Key key) {
        final valueKey = key as ValueKey;
        final index = items.indexWhere((item) => item == valueKey.value);
        if (index == -1) return null;
        return index;
      },
    );
  }
}

Here the dartpad https://dartpad.dev/?id=b02294906a0b0c77984126e62f5a048d.

  • Tap any items on list, so it will become selected
  • Add new item using FAB, it will insert new element into random index of listview
  • After added new item, the already selected (state) item(s) will remain selected
Alann Maulana
  • 1,170
  • 12
  • 15
  • Hi @Alann, thanks for the reply. However this doesn't work and i had already tried this solution. It doesn't work in my case because i have different kind of Stateful widgets in my `ListView.builder` (chat app with audios / videos). I have tried again with this code, but when i insert a text message when playing (and displaying) an audio widget, it goes again inside the init state of the audio because the ListView see that the child at the old index has a different state. – Tom3652 Jun 10 '22 at 13:51
  • That's why i want to use the method `findChildIndexCallback` mentioned above, your implementation works for basic lists but it's not my requirement unfortunately :/ – Tom3652 Jun 10 '22 at 13:51
  • 1
    Yeah, some real life production code will need a lot of improvements to make it works. It's a bit difficult to get the idea to re-create your ListView item code :) – Alann Maulana Jun 11 '22 at 01:16
  • Thanks for the suggestion anyway, but yeah that's why i have specifically asked about this method usage because the Flutter dev team told me to use it but i can't understand how :/ and it seems simple but i am lost using `ValueKey` objects and how to reuse them... And this is why i have linked my github issue as well :) – Tom3652 Jun 11 '22 at 14:46
  • I think I miss the collapsible section for example code :) will try it – Alann Maulana Jun 11 '22 at 15:25
  • 1
    Already trying your sample code from github, it just works by adding `findChildIndexCallback` into your `ListView.builder` on dartpad. It's ok, until adding 16th item, the 1st item is disposed. Add the 17th item, the 2nd one is disposed. It seems ListView will dispose any widget that not visible on screen. – Alann Maulana Jun 11 '22 at 15:37
  • Could you please provide the example code working using `findChildIndexCallback` ? I actually want the dispose behavior when items are not visible, but only keep the state of visible items while inserting so the behavior you describe is exactly the one i want! – Tom3652 Jun 11 '22 at 15:52
  • Check your github issue, I send a comment about `findChildIndexCallback` code and some result :) or check my dartpad answer above for line 63-68 – Alann Maulana Jun 11 '22 at 15:56
  • 1
    thanks for your answer there too ! let me check this out with my complex widgets and hopefully this will work! – Tom3652 Jun 11 '22 at 16:02
  • I confirm that it's working fine now, thanks for your help, it's similar to the code i had written but i must have done something wrong at some point... Anyway it's working :) – Tom3652 Jun 13 '22 at 15:33