website/docs/concepts2/refs.mdx
import { Link } from "/src/components/Link"; import { AutoSnippet, } from "/src/components/CodeSnippet";
Refs are the primary way to interact with <Link documentID="concepts2/providers"/>.
Refs are fairly similar to the BuildContext in Flutter, but for providers instead of widgets.
A non-exhaustive list of things you can do with a ref:
On top of that, Ref also enables a provider to observe life-cycles about its own state. Think "initState" and "dispose", but for providers. This includes methods such as:
Obtaining a Ref depends on where you are in your app.
Providers naturally have access to a Ref. You can find it as parameter of the initializer function, or as a property of Notifier classes.
<AutoSnippet codegen={` @riverpod int myProvider(Ref ref) { // ref is available here ... }
@riverpod
class MyNotifier extends _$MyNotifier {
@override
int build() {
// this.ref is available anywhere inside notifiers
ref.watch(someProvider);
...
}
}
} raw={
final myProvider = Provider<int>((ref) {
// ref is available here
...
});
final myNotifierProvider = NotifierProvider<MyNotifier, int>(MyNotifier.new);
class MyNotifier extends Notifier<int> { @override int build() { // this.ref is available anywhere inside notifiers ref.watch(someProvider); ... } } `} />
To obtain a Ref inside widgets, you need <Link documentID="concepts2/consumers"/>.
Consumer(
builder: (context, ref, _) {
// ref is available here
final value = ref.watch(myProvider);
return Text('$value');
},
);
I am never inside a widget, nor a provider. How do I get a Ref then?
If you are neither inside widgets nor providers, chances are whatever you are using
is still loosely connected to a widget/provider.
In that case, simply pass the ref you obtained from your widget/provider to your function/object of choice:
void myFunction(WidgetRef ref) {
// You can pass the ref around!
}
...
Consumer(
builder: (context, ref, _) {
return ElevatedButton(
onPressed: () => myFunction(ref), // Pass the ref to your function
child: Text('Click me'),
);
},
);
Interactions with providers generally fall under two categories:
Riverpod offers two ways to listen to a provider's state:
For the following examples, consider a provider that updates every second:
<AutoSnippet
codegen={ @riverpod class Tick extends _$Tick { @override int build() { final timer = Timer.periodic(Duration(seconds: 1), (_) => state++); ref.onDispose(timer.cancel); return 0; } } }
raw={ final tickProvider = NotifierProvider<Tick, int>(Tick.new); class Tick extends Notifier<int> { @override int build() { final timer = Timer.periodic(Duration(seconds: 1), (_) => state++); ref.onDispose(timer.cancel); return 0; } } }
/>
Ref.watch is Riverpod's defining feature. It enables combining providers together seamlessly, and easily have your UI update when a provider's state changes.
Using Ref.watch is similar to using an InheritedWidget in Flutter.
In Flutter, when you call Theme.of(context), your widget subscribes to the Theme
and will rebuild whenever the Theme changes. Similarly, when you call ref.watch(myProvider),
your widget/provider subscribes to myProvider, and will rebuild whenever myProvider changes.
The following code shows a <Link documentID="concepts2/consumers"/> that automatically updates whenever our Tick provider updates:
Consumer(
builder: (context, ref, _) {
final tick = ref.watch(tickProvider);
return Text('Tick: $tick');
},
);
The most interesting part of Ref.watch is that providers can use it too!
For example, we could create a provider that returns "is tick divisible by 4?":
<AutoSnippet
codegen={ @riverpod bool isDivisibleBy4(Ref ref) { final tick = ref.watch(tickProvider); return tick % 4 == 0; } }
raw={ final isDivisibleBy4 = Provider<bool>((ref) { final tick = ref.watch(tickProvider); return tick % 4 == 0; }); }
/>
Then, we could listen to this new provider in our UI instead:
Consumer(
builder: (context, ref, _) {
final isDivisibleBy4 = ref.watch(isDivisibleBy4Provider);
return Text('Can tick be divided by 4? ${isDivisibleBy4}');
},
);
Now, instead of updating every second, our UI will only update when the boolean value changes.
Ref.listen is a more manual way of listening to providers.
It is similar to the addListener method of ChangeNotifier, or the Stream.listen method.
This method is useful when you want to perform a side-effect when a provider's state changes, such as
<AutoSnippet codegen={` @riverpod int example(Ref ref) { ref.listen(tickProvider, (previous, next) { // This is called whenever tickProvider changes print('Tick changed from $previous to $next'); });
return 0;
}
} raw={
final exampleProvider = Provider<int>((ref) {
ref.listen(tickProvider, (previous, next) {
// This is called whenever tickProvider changes
print('Tick changed from $previous to $next');
});
return 0;
}); `} />
Consumer(
builder: (context, ref, _) {
ref.listen(tickProvider, (previous, next) {
// This is called whenever tickProvider changes
print('Tick changed from $previous to $next');
});
return Text('Listening to tick changes');
},
);
:::note
It is safe to use WidgetRef.listen inside the build method of a widget. This is how the
method is designed to be used.
If you want to listen to providers outside of build (such as State.initState), use WidgetRef.listenManual instead.
:::
Using Ref.invalidate, you can reset a provider's state.
This will tell Riverpod to discard the current state and re-evaluate the provider the next time it is read.
The following example will reset the tick to 0:
Consumer(
builder: (context, ref, _) {
return ElevatedButton(
onPressed: () {
// Reset the tick provider
// This will restart the tick from 0
ref.invalidate(tickProvider);
},
child: Text('Reset Tick'),
);
},
);
:::tip If you need to obtain the new state right after resetting it, you can call Ref.read:
ref.invalidate(tickProvider);
final newTick = ref.read(tickProvider);
Alternatively, you can use Ref.refresh to reset the provider and read its new state in one go:
final newTick = ref.refresh(tickProvider);
Both codes are strictly equivalent. Ref.refresh is syntax sugar for Ref.invalidate followed by Ref.read. :::
A last use-case is to interact with a provider's state inside button clicks. In this scenario, we do not want to "listen" to the state. For this case, Ref.read exists.
You can safely call Ref.read button clicks to perform work. The following example will print the current tick value when the button is clicked:
Consumer(
builder: (context, ref, _) {
return ElevatedButton(
onPressed: () {
// Read the current tick value
final tick = ref.read(tickProvider);
print('Current tick: $tick');
},
child: Text('Print Tick'),
);
},
);
:::danger Do not use Ref.read as a mean to "optimize" your code by avoiding Ref.watch. This will make your code more brittle, as changes in your provider's behavior could cause your UI to be out of sync with the provider's state.
Either use Ref.watch anyway (as the difference is negligible) or use select:
Consumer(
builder: (context, ref, _) {
// ❌ Don't use "read" as a mean to ignore changes
final tick = ref.read(tickProvider);
// ✅ Use "watch" to listen to changes.
// This shouldn't be a bottle-neck in your apps. Do not over-optimize.
final tick = ref.watch(tickProvider);
// ✅ Use "select" to only listen to the specific part of the state you care about
final isEven = ref.watch(
tickProvider.select((tick) => tick.isEven),
);
...
},
);
:::
A provider-specific feature of Ref is the ability to listen to life-cycle events.
These events are similar to the initState, dispose, and other life-cycle methods in Flutter widgets.
Life-cycles listeners are registered using an "addListener" style API.
Listeners are methods with a name that starts with on, such as onDispose or onCancel.
<AutoSnippet codegen={` @riverpod int counter(Ref ref) { ref.onDispose(() { // This is called when the provider is disposed print('Counter provider is being disposed'); });
return 0;
}
} raw={
final counterProvider = Provider<int>((ref) {
ref.onDispose(() {
// This is called when the provider is disposed
print('Counter provider is being disposed');
});
return 0;
}); `} />
:::tip
You do not need to "unregister" these listeners.
Riverpod automatically cleans them up when the provider is reset.
Although if you wish to unregister them manually, you can do so by using the return value of the listener method.
final unregister = ref.onDispose(() {
print('This will never be called');
});
// This will unregister the "onDispose" listener
unregister();
:::