FAQ
Here are some commonly asked questions from the community:
What is the difference between ref.refresh
and ref.invalidate
?
You may have noticed that ref
has two methods to force a provider to recompute,
and wonder how they differ.
It's simpler than you think: ref.refresh
is nothing but syntax sugar for
invalidate
+ read
:
T refresh<T>(provider) {
invalidate(provider);
return read(provider);
}
If you do not care about the new value of a provider after recomputing it,
then invalidate
is the way to go.
If you do, use refresh
instead.
This logic is automatically enforced through lint rules.
If you tried to use ref.refresh
without using the returned value,
you would get a warning.
The main difference in behavior is by reading the provider right
after invalidating it, the provider immediately recomputes.
Whereas if we call invalidate
but don't read it right after,
then the update will trigger later.
That "later" update is generally at the start of the next frame. Yet, if a provider that is currently not being listened to is invalidated, it will not be updated until it is listened to again.
Why is there no shared interface between Ref and WidgetRef?
Riverpod voluntarily dissociates Ref
and WidgetRef
.
This is done on purpose to avoid writing code which conditionally
depends on one or the other.
One issue is that Ref
and WidgetRef
, although similar looking,
have subtle differences.
Code relying on both would be unreliable in ways that are difficult to spot.
At the same time, relying on WidgetRef
is equivalent to relying on BuildContext
.
It is effectively putting your logic in the UI layer, which is not recommended.
Such code should be refactored to always use Ref
.
The solution to this problem is typically to move your logic
into a Notifier
(see Performing side effects),
and then have your logic be a method of that Notifier
.
This way, when your widgets want to invoke this logic, they can write something along the lines of:
ref.read(yourNotifierProvider.notifier).yourMethod();
yourMethod
would use the Notifier
's Ref
to interact with other providers.
Why do we need to extend ConsumerWidget instead of using the raw StatelessWidget?
This is due to an unfortunate limitation in the API of InheritedWidget
.
There are a few problems:
It is not possible to implement an "on change" listener with
InheritedWidget
. That means that something such asref.listen
cannot be used withBuildContext
.State.didChangeDependencies
is the closest thing to it, but it is not reliable. One issue is that the life-cycle can be triggered even if no dependency changed, especially if your widget tree uses GlobalKeys (and some Flutter widgets already do so internally).Widgets listening to an
InheritedWidget
never stop listening to it. This is usually fine for pure metadata, such as "theme" or "media query".For business logic, this is a problem. Say you use a provider to represent a paginated API. When the page offset changes, you wouldn't want your widget to keep listening to the previously visible pages.
InheritedWidget
has no way to track when widgets stop listening to them. Riverpod sometimes relies on tracking whether or not a provider is being listened to.
This functionality is crucial for both the auto dispose mechanism and the ability to
pass arguments to providers.
Those features are what make Riverpod so powerful.
Maybe in a distant future, those issues will be fixed. In that case,
Riverpod would migrate to using BuildContext
instead of Ref
.
This would enable using StatelessWidget
instead of ConsumerWidget
.
But that's for another time!
Why doesn't hooks_riverpod exports flutter_hooks?
This is to respect good versioning practices.
While you cannot use hooks_riverpod
without flutter_hooks
,
both packages are versioned independently. A breaking change could happen
in one but not the other.
Why does Riverpod use identical
instead of ==
to filter updates in some cases?
Notifiers use identical
instead of ==
to filter updates.
This is because it is quite common for Riverpod users to also use a code-generator
such as Freezed/built_value for the sake of a copyWith implementation. Those packages
override ==
to deeply compare objects. A deep object comparison is quite costly.
"Business logic" models tend to have lots of properties. Worse, they also have collections
such as lists, maps, and so on.
At the same time, when using complex "business" objects, most state = newState
invocations
always result in a notification (otherwise there is no point in calling the setter). Generally, the main
case where we call state = newState
when the current state and new states are equal is
for primitive objects (ints, enums, strings, but not lists/classes/...).
These objects are "canonicalized by default". If such objects are equal,
they generally are also "identical".
Riverpod using identical
to filter updates is therefore an attempt at having
a good default for both worlds. For complex objects where we don't really care
about filtering objects and where ==
may be expensive due to code-generators
generating an ==
override by default, using identical
provides an efficient way of notifying listeners.
At the same time, for simple objects, identical
does correctly filter redundant notifications.
Last but not least, you can change this behavior by overriding the method
updateShouldNotify
on Notifiers.
Is there a way to reset all providers at once?
No, there is no way to reset all providers at once.
This is on purpose, as it is considered an anti-pattern. Resetting all providers at once will often reset providers that you did not intend to reset.
This is commonly asked by users who want to reset the state of their application
when the user logs out.
If this is what you are after, you should instead have everything dependent on the
user's state to ref.watch
the "user" provider.
Then, when the user logs out, all providers depending on it would automatically be reset but everything else would remain untouched.
I have the error "Cannot use "ref" after the widget was disposed", what's wrong?
You might also see "Bad state: No ProviderScope found", which is an older error message of the same issue.
This error happens when you try to use ref
in a widget that is no longer
mounted. This generally happens after an await
:
ElevatedButton(
onPressed: () async {
await future;
ref.read(...); // May throw "Cannot use "ref" after the widget was disposed"
}
)
The solution is to, like with BuildContext
, check mounted
before using ref
:
ElevatedButton(
onPressed: () async {
await future;
if (!context.mounted) return;
ref.read(...); // No longer throws
}
)