Flutter Riverpod Listener plays an important role when it comes to detecting state that are available. It’s more like BLocConsumer listener. Based on this listener object we will be able to trigger new routes or screen pop or push. This could be use for showing snackBar.
Riverpod Listener stores the earlier states.
Of course, It’s available in the listen() method of our so called ref object.
ref.listen() is used to listen to state change using provider of Riverpod.
listen() itself takes a provider, a listener and an error handler.
void listen (
ProviderListenable provider,
void Function(T?, T) listener,
{
void Function(Object, StackTrace)? onError,
})
This is all about listener object or callback inside listen() function and we mostly use it by passing a provider. If you notice carefully listener take a callback function and object as a parameter.
And then what does this listener function do? Official document did not provide much information. So I dedided to explain with example.Now took at the code below and the marked code.
The above code is part of presentation layer, part of login screen.
See on line 20, we have a Provider, and this we pass to ref.listen() function on line 22. And then on line 23, we are listening to states.
In general we the second item or argument refers to one of the state variables or objects. So what’s the state object then?.
State objects refer to the variables or objects that should change on interaction and based on that UI should be updated.
These state variables are more like Getx obs or BLoC state objects. Let’s look at some the state variables I have used in the application.
Here I have declared state variables. If these states are triggered or emitted we would be able to catch them in our presentation layer.
We know our presentation layer contains screens and widgets. Our presentation layer also contains state variables.
Let’s take a look at our presentation providers layer. Here we will see that we are triggering states or changes based on user interaction.
You may see, you try to login as a user by tapping on a button and a method loginUser() get triggered, we are waiting for three states like
- AuthState.loading()
- AuthState.failure()
- AuthState.success()
Once one of these are triggered we wanna catch them on the UI. These states are already defined in the cod of second picture.
We trigger based on external conditions. Regardless loading, failure or success any one is triggered, we will catch on the user.
This is where Riverpod ref.listen() and listener comes into play. Whatever is triggered (loading, failure or success), we catch them inside listener callback as previous or next object.
In general next refers to one of the above three states. Whatever is triggered next would be equal to either loading, failure or success.
Whatever next object matches to we do a conditional check and trigger routes ro go to a new page.
See how new routes triggered based on the conditional match of next with state variables.
As I said early, Riverpod Listener is more like BlocConsumer and let’s see it from from the picture below.
But Riverpod Listener, you can not use inside body property which you can for BLoC. You need to make sure you are listening to Riverpod Listener inside build method.
Now let’s look at an example
void main() {
runApp(ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
// first provider
final helloStringProvider = StateProvider<String>((ref) {
return 'Hello';
});
final numberProvider = StateProvider<num>((ref) {
return 1;
});
class HomePage extends ConsumerWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final value = ref.watch(numberProvider);
ref.listen(numberProvider, (previousValue, newValue) {
print('Number Changed: $newValue');
});
return Scaffold(
appBar: AppBar(
title: const Text('Riverpod'),
),
body: Center(
child: ElevatedButton(
onPressed: (){
ref.read(numberProvider.notifier).state++;
},
child: Text(
value.toString(),
style: Theme.of(context).textTheme.headline2,
),
),
),
);
}
}
From the above example you see that, we can store our states and get them from previous and next object.
And that means you can do anything inside the code block.