avoid_ref_read_inside_build
v0.4.0 Warning Fix Riverpod State
This rule flags a one-off provider read inside a build() method. Such a read fetches the value once and does not subscribe, so the widget will not rebuild when the provider updates.
It covers both ecosystems, because the mistake is identical in each:
One-off (flagged in build) |
Subscribing (correct in build) |
|
|---|---|---|
| Riverpod | ref.read(...) |
ref.watch(...) |
| package:provider | context.read<T>() |
context.watch<T>() |
Why use this rule
Section titled “Why use this rule”A one-off read in build() is almost always a mistake. The widget renders with whatever value the provider had at that moment and is never told it changed, so the UI goes stale until something unrelated rebuilds it — a bug that reproduces only in the order the user happened to tap things.
A one-off read inside a callback (onPressed) is intentional and is not reported.
The two ecosystems are told apart by the receiver’s resolved type, not by its name: Riverpod’s ref is a WidgetRef/Ref, while provider’s extensions hang off BuildContext. So widgetContext.read<T>() is caught under any receiver name, while a field of some unrelated class you happened to call ref is not.
See also: Riverpod refs, provider: read vs watch
// Riverpodclass MyWidget extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { // Reads once, never rebuilds on changes final value = ref.read(someProvider); return Text(value); }}
// package:providerclass MyOtherWidget extends StatelessWidget { @override Widget build(BuildContext context) { final value = context.read<String>(); return Text(value); }}class MyWidget extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { // Subscribes and rebuilds when provider changes final value = ref.watch(someProvider); return Text(value); }}
// package:providerclass MyProviderWidget extends StatelessWidget { @override Widget build(BuildContext context) { final value = context.watch<String>(); return Text(value); }}
// A one-off read inside a callback is fine, in either ecosystem.class MyOtherWidget extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { return ElevatedButton( onPressed: () { // Intentional one-time read triggered by user action final value = ref.read(someProvider); }, child: const Text('Tap'), ); }}Configuration
Section titled “Configuration”This rule is in the recommended preset, so it is on with
preset: recommended or preset: opinionated. Add it to preset: core with
avoid_ref_read_inside_build: true.
To turn it off:
rules: avoid_ref_read_inside_build: falseTo keep the rule on but skip certain paths, use per-rule exclude.
Related rules
Section titled “Related rules”avoid_ref_inside_state_dispose— Avoid accessing ref inside the dispose method of a ConsumerState.avoid_ref_watch_outside_build— Subscribe only in build; read once everywhere else.avoid_build_context_in_providers— Providers outlive widgets, so they should not receive a BuildContext.notifier_build— Classes annotated with @riverpod must define a build method.