I've got a List<Data>
which is diplayed in a ListView
that uses Riverpod to watch any changes to the list. When I add or remove an item from that list, the ListView
rebuilds as intended, but it appears like every ListViewItem
and its descending widgets are rebuild - even though they show the same content as before. Here's a simplified version of my code:
class MyApp extends ConsumerWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final listLength = ref.watch(dataLengthProvider);
return MaterialApp(
home: Scaffold(
body: Column(
children: [
ElevatedButton(
child: const Icon(Icons.add),
onPressed: () => ref.read(dataListProvider.notifier).add(),
),
Expanded(
child: ListView.builder(
itemCount: listLength,
itemBuilder: (context, index) {
return MyListItem(index);
},
),
),
],
),
),
);
}
}
class MyListItem extends ConsumerWidget {
final int index;
const MyListItem(this.index, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final countValue =
ref.watch(dataItemProvider(index).select((dataItem) => dataItem.value));
return Text('Value: ${countValue.toString()}');
}
}
// Providers -------------------------------------------------------------------
final dataListProvider = StateNotifierProvider<DataListNotifier, List<Data>>(
(ref) => DataListNotifier());
final dataLengthProvider =
Provider<int>((ref) => ref.watch(dataListProvider).length);
final dataItemProvider = Provider.family<Data, int>(
(ref, index) => ref.watch(dataListProvider)[index]);
// Notifier --------------------------------------------------------------------
class DataListNotifier extends StateNotifier<List<Data>> {
DataListNotifier() : super([const Data(), const Data()]);
void add() {
state = [...state, const Data(value: 0)];
}
}
// Data model ------------------------------------------------------------------
@immutable
class Data {
final int value;
const Data({this.value = 0});
Data copyWith({int? newValue}) => Data(value: newValue ?? value);
}
Now my question: Is Flutter smart enough to automatically re-use those unchanged widgets?
If not, what can I do to avoid unneccessary builds?
CodePudding user response:
You can check something. To do this, remake your class MyListItem
in to have access to dispose():
class MyListItem extends ConsumerStatefulWidget {
final int index;
const MyListItem(
this.index, {
Key? key,
}) : super(key: key);
@override
ConsumerState createState() => _MyListItemState();
}
class _MyListItemState extends ConsumerState<MyListItem> {
@override
Widget build(BuildContext context) {
print(widget.index);
final countValue = ref.watch(
dataItemProvider(widget.index).select((dataItem) => dataItem.value));
return Text('Value: ${countValue.toString()}');
}
@override
void dispose() {
print('dispose: ${widget.index}');
super.dispose();
}
}
and add method delete()
near add()
:
void delete() {
state.removeLast();
state = List.of(state);
}
and add button in MyApp
:
ElevatedButton(
child: const Icon(Icons.delete),
onPressed: () => ref.read(dataListProvider.notifier).delete(),
),
And check this code again. There, of course, the RangeError (index)
error will be raised, but this is not the point. But on the other hand, you can see that the dispose()
method is not called when the element is added, which means that the object is not removed from the tree. At the same time, when the last element is removed, we can see the call to the dispose()
method, but only for the last element! So you are on the right track :)
CodePudding user response:
You can use the select
for getting the reference of the provider for stopping unnecessary rebuilds in the list item.
https://riverpod.dev/docs/concepts/reading/#using-select-to-filter-rebuilds