provider 6.0.5 (current latest)
Flutter 3.13.7
Dart 3.1.3
https://dartpad.dev/?id=50e340da1ea3c75f0aa66a32394db32d
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: ChangeNotifierProvider<MyState>(
create: (context) => MyState(),
child: const Body(),
),
),
);
}
}
class MyState with ChangeNotifier {
List<int> values = [0, 0, 0];
void increment(int key) {
values[key]++;
notifyListeners();
}
}
class Body extends StatelessWidget {
const Body({super.key});
@override
Widget build(BuildContext context) {
var values = context.watch<MyState>().values;
return Column(
children: [
Row(
children: [
for (int i = 0; i < values.length; i++)
Selector<MyState, int>(
selector: (_, state) {
print("Selector index: $i, selector: ${state.values[i]}");
return state.values[i];
},
shouldRebuild: (previous, next) {
print(
"Should index: $i, prev: $previous, next: $next ${previous != next ? ", NEED REBUILD" : ""}");
return previous != next;
},
builder: (_, __, ___) {
print("Build index: $i");
return Expanded(
child: Center(
child: Text(
"${values[i]}",
textScaleFactor: 3,
)));
},
)
],
),
Row(
children: [
for (int i = 0; i < values.length; i++)
Expanded(
child: Center(
child: TextButton(
onPressed: () {
print("-" * 20);
print("Increment index: $i");
context.read<MyState>().increment(i);
},
child: Text("Increment $i"),
),
),
)
],
),
Center(
child: Text(
"Summ: ${values.reduce((value, element) => value + element)}",
textScaleFactor: 2,
),
),
],
);
}
}
Selector index: 0, selector: 0
Build index: 0
Selector index: 1, selector: 0
Build index: 1
Selector index: 2, selector: 0
Build index: 2
--------------------
Increment index: 1
Selector index: 0, selector: 0
Build index: 0
Selector index: 1, selector: 1
Build index: 1
Selector index: 2, selector: 0
Build index: 2
build
is always called.
shouldRebuild
is never called.
What am I doing wrong?
If change
final shouldInvalidateCache = oldWidget != widget ||
to
final shouldInvalidateCache = oldWidget == null ||
in provider/lib/src/selector.dart
then all work good:
Increment index: 1
Selector index: 0, selector: 0
Should index: 0, prev: 0, next: 0
Selector index: 1, selector: 1
Should index: 1, prev: 0, next: 1 , NEED REBUILD
Build index: 1
Selector index: 2, selector: 0
Should index: 2, prev: 0, next: 0
The problem is the .watch
methods rebuilds the tree, which rebuilds Selector
each time, making it never get to shouldRebuild
.
Solution: Change the watch method to read, cause it's only needed once to build the related widgets.
// change
var values = context.watch<MyState>().values;
// to
var values = context.read<MyState>().values;
Instead of using .watch
results to update the sum, use a consumer.
// change
Text(
"Summ: ${values.reduce((value, element) => value + element)}",
textScaleFactor: 2,
),
//to
Consumer<MyState>(
builder: (context, state, child) {
return Text(
"Summ: ${state.values.reduce((value, element) => value + element)}",
textScaleFactor: 2,
);
},
)
Bonus You may access the MyState value from builder in selector.
// this
builder: (_, __, ___) {
return Expanded(
child: Center(
child: Text(
"${values[i]}",
textScaleFactor: 3,
),
),
);
},
// to
builder: (_, value, ___) {
return Expanded(
child: Center(
child: Text(
"$value",
textScaleFactor: 3,
),
),
);
},