Reputation: 5377
According to Flutter's documentation and this example, as I'm understanding it, a key difference between the Provider package's context.read<T>
and context.watch<T>
methods relate to triggering widget rebuilds. You can call context.watch<T>()
in a build
method of any widget to access current state, and to ask Flutter to rebuild your widget anytime the state changes. You can't use context.watch<T>()
outside build methods, because that often leads to subtle bugs. Instead, they say, use context.read<T>()
, which gets the current state but doesn't ask Flutter for future rebuilds.
I tried making this simple app:
class MyDataNotifier extends ChangeNotifier {
String _testString = 'test';
// getter
String get testString => _testString;
// update
void updateString(String aString) {
_testString = aString;
notifyListeners();
}
}
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => MyDataNotifier(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text(context.read<MyDataNotifier>().testString),
),
body: Container(
child: Level1(),
),
),
);
}
}
class Level1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
onChanged: (val) {
context.read<MyDataNotifier>().updateString(val);
},
),
Text(context.read<MyDataNotifier>().testString),
],
);
}
}
All the calls are to counter.read<T>()
. The app's state changes, but the UI is not rebuilt with the new value. I have to change one of the calls to counter.watch<T>()
to get the state to rebuild.
On the other hand, in DZone's simple example, the UI rebuilds, and all the calls are to context.read().
What's different between their code and mine? Why can't I rebuild with counter.read() calls?
Upvotes: 1
Views: 2583
Reputation: 10136
TLDR: after a quick glance, the DZone article looks like it has a bug.
context.watch<Foo>()
does 2 things:
context
as dependent on Foo
context.read<Foo>()
only does 1).
Whenever your UI depends on Foo
, you should use context.watch
, since this appropriately informs Flutter about that dependency, and it will be rebuilt properly.
In general, it boils down to this rule of thumb:
context.watch
in build()
methods, or any other method that returns a Widget
context.read
in onPressed
handlers (and other related functions)The main reason people seem to use context.read
inappropriately is for performance reasons. In general, preferring context.read
over context.watch
for performance is an anti-pattern. Instead, you should use context.select
if you want to limit how often a widget rebuilds. This is most useful whenever you have a value that changes often.
Imagine you have the following state:
class FooState extends ChangeNotifier {
// imagine this us updated very often
int millisecondsSinceLastTap;
// updated less often
bool someOtherProperty = false;
}
If you had a widget that displays someOtherProperty
, context.watch
could cause many unnecessary rebuilds. Instead, you can use context.select
only depend on a processed part of the state:
// read the property, rebuild only when someOtherProperty changes
final property = context.select((FooState foo) => foo.someOtherProperty);
return Text('someOtherProperty: $property');
Even with a frequently updating value, if the output of the function provided to select doesn't change, the widget won't rebuild:
// even though millisecondsSinceLastTap may be updating often,
// this will only rebuild when millisecondsSinceLastTap > 1000 changes
final value = context.select((FooState state) => state.millisecondsSinceLastTap > 1000);
return Text('${value ? "more" : "less"} than 1 second...');
Upvotes: 5