async_value_nullable_pattern
v0.8.0 Warning Fix Riverpod State
Flags AsyncValue(:final value?) when the value type is nullable. The ? pattern matches only when the value is non-null, which is not the same question as whether a value has loaded.
Why use this rule
Section titled “Why use this rule”For an AsyncValue<int?>, a successfully loaded null is a real result. The ? pattern rejects it, so that case silently falls through to the loading or error branch — the UI shows a spinner forever for data that actually arrived. hasValue: true asks the question you meant: has this loaded?
See also: Riverpod - AsyncValue
A profile screen whose provider yields User? — null meaning “signed out”,
which is a perfectly good loaded value. The ? pattern rejects it, so a
signed-out user gets a spinner that never stops:
final currentUserProvider = FutureProvider<User?>((ref) => auth.currentUser());
class ProfileView extends ConsumerWidget { const ProfileView({super.key});
@override Widget build(BuildContext context, WidgetRef ref) { final user = ref.watch(currentUserProvider);
return switch (user) { AsyncValue(:final value?) => Text(value.name), // LINT AsyncValue(:final error?) => Text('$error'), _ => const CircularProgressIndicator(), }; }}Ask the question you meant — has this loaded? — and handle the null yourself:
final currentUserProvider = FutureProvider<User?>((ref) => auth.currentUser());
class ProfileView extends ConsumerWidget { const ProfileView({super.key});
@override Widget build(BuildContext context, WidgetRef ref) { final user = ref.watch(currentUserProvider);
return switch (user) { AsyncValue(:final value, hasValue: true) => Text(value?.name ?? 'Signed out'), AsyncValue(:final error?) => Text('$error'), _ => const CircularProgressIndicator(), }; }}Known limitations
Section titled “Known limitations”The rule stays silent where the ? pattern is already precise.
A non-nullable value type — null can then only mean “not loaded”, which is
exactly what ? asks:
void fn(AsyncValue<int> asyncValue) { switch (asyncValue) { case AsyncValue(:final value?): print(value); default: break; }}Matching AsyncData rather than AsyncValue — AsyncData.hasValue is always
true, so the null check is doing real work:
void onData(AsyncValue<int?> asyncValue) { switch (asyncValue) { case AsyncData(:final value?): print(value); default: break; }}Configuration
Section titled “Configuration”This rule is in the core preset, so it is on with preset: core,
preset: recommended or preset: opinionated.
To turn it off:
rules: async_value_nullable_pattern: falseTo keep the rule on but skip certain paths, use per-rule exclude.
Related rules
Section titled “Related rules”avoid_build_context_in_providers— Providers outlive widgets, so they should not receive a BuildContext.avoid_ref_inside_state_dispose— Avoid accessing ref inside the dispose method of a ConsumerState.avoid_ref_read_inside_build— Subscribe in build; do not read once.avoid_ref_watch_outside_build— Subscribe only in build; read once everywhere else.