There are a bunch of misconceptions related to ListView.
First, ListView.builder does not "always rebuilds the whole widget" in the sense that it rebuilds all children. Instead, it only rebuilds the necessary children, i.e. the ones visible. If your ListView is rebuilding all children, check your code, there is something wrong with it.
Second, official docs actually recommend using ListView.builder
or ListView.separated
when working with long lists. (Docs)
Third, there is NO WAY to completely control what is built by the ListView using any constructor. I would love for somebody to prove me wrong on this point. If that was the case, there would be a builder with a callback on which children to rebuild. There isn't. And that is not what findChildIndexCallback
does.
Fourth, ListView.custom
with findChildIndexCallback
is useful to preserve the state of the child.
From the docs: findChildIndexCallback property (Link)
If not provided, a child widget may not map to its existing
RenderObject when the order of children returned from the children
builder changes. This may result in state-loss.
That is, if you NEED to CHANGE the state of the widget AND MAINTAIN, this is useful. Again, change and keep. In most cases this is not needed.
In summary, building and rebuilding is not expensive, as long as your data is loaded upfront using init. If you need to manipulate the source data (like loading images) you can do it using any constructor.
To better understand the builder, try the code below and scroll up. You will understand that the builder is called for the items 'randomly' from around 11 to 14. Full code:
import 'package:flutter/material.dart';
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(context) => MaterialApp(
home: const MyListView(),
);
}
class MyListView extends StatefulWidget {
const MyListView({Key? key}) : super(key: key);
@override
State<MyListView> createState() => _MyListViewState();
}
class _MyListViewState extends State<MyListView> {
final items = List<String>.generate(10000, (i) => 'Item $i');
void _addItems() {
setState(() {
items.add("asdf");
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
print(index);
return ListTile(
title: Text(items[index]),
);
},
),
),
bottomNavigationBar: BottomAppBar(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextButton(
onPressed: () => _addItems(),
child: const Text('Add items'),
),
],
),
),
);
}
}