Yes, it’s overrated and difficult for beginners. Now, I am not saying this because I love other state management package or hate Riverpod. It causes problem and impossible to keep track of the error once you use Notifier Provider and AsyncNotifier Provider together.
Riverpod does come with it’s benefit. Like AsyncLoading, AsyncData, AsyncValue. These are advanced topic though. And it also does a good job of error handling.
Most beginners might not know about this. In fact most cases you don’t need to use them. So simple dummy app or small project it might work better.
But when you work with big projects, it might not work well at all. In Fact you may get trap within the ref.read() and ref.watch() magic.
A simple counter app example would not work if you call ref.watch(counterValueProvider).
Even if you are not going to use ref.watch(counterValueProvider) at all. But this is not very intuitive to put at the top of your build() method.
Documentation also did not say it clearly that without ref.watch(counterValueProvider), it’s not going to work at all.
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: navKey,
title: 'Flutter Demo',
theme: AppTheme.appThemeData,
//onGenerateRoute: AppPages.generateRouteSettings,
home: MyHomePage(),
);
}
}
@riverpod
class HomeIndexController extends _$HomeIndexController{
@override
int build(){
return 0;
}
void increment(){
state = state+1;
print(state);
}
void decrement(){
state = state-1;
print(state);
}
}
class MyHomePage extends ConsumerWidget {
const MyHomePage({
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
ref.watch(homeIndexControllerProvider);
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text("Riverpod app"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
"Test",
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
FloatingActionButton(
heroTag: "one",
onPressed: (){
ref.read(homeIndexControllerProvider.notifier).increment();
},
tooltip: 'Increment',
child: Icon(Icons.arrow_right_rounded),
),
FloatingActionButton(
heroTag: "one",
onPressed: (){
ref.read(homeIndexControllerProvider.notifier).decrement();
},
tooltip: 'Increment',
child: Icon(Icons.arrow_right_rounded),
),
],
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
If you remove ref.watch(homeIndexControllerProvider), You will find it does not work. Even though you try to increment the value It won’t get increased.
Well I am not using ref.watch(homeIndexControllerProvider), So it does not make sense to put it at the top.
Documentation did not say, we must have it somewhere in our code.
But to have state to persist and work on them, look like you have to use @Riverpod(keepAlive:true).
If you use the above annotation you can keep the provider value insistent. But this should be unnecessary and creating complexity.
Since I have this @Riverpod(keepAlive:true), now the state is persistent. But with BLoC or Getx you did not have to do this.
Once you use ref.read() with notifier, it may cause unnecessary dependency changes which cause rebuild of whole state. This also means you lose the data.
It’s easy to lose state variable value or not updating state value at all.
You need to extremely careful when you use autoDispose and @Riverpod(keepAlive:true) together. Otherwise you will have strange behaviour for complex logical problems and data processing.
Notifier and AsyncNotifier using together can cause great error. But these errors are not reported and just causes time waste.
Good article about riverpod, thanks for share!