Riverpod was built by the same author as Provider, as a redesign that fixes Provider’s biggest gaps: no compile-time safety for missing providers, no built-in handling for async loading/error states, and awkward testing. If you already know Provider, most of the concepts here will feel familiar under new names.
Setup
dependencies:
flutter_riverpod: ^2.5.0
Wrap your app in a ProviderScope — this is the one mandatory setup step, and it replaces the need to wire up a provider tree manually the way Provider requires:
void main() {
runApp(const ProviderScope(child: MyApp()));
}
Your First Provider
A simple, read-only value uses Provider. For state that changes, use NotifierProvider with a Notifier class:
class Counter extends Notifier<int> {
@override
int build() => 0;
void increment() => state++;
}
final counterProvider = NotifierProvider<Counter, int>(Counter.new);
build() returns the initial state. Anywhere else in the notifier, assigning to state both updates the value and notifies listeners — there’s no separate notifyListeners() call to remember, unlike ChangeNotifier.
Reading It in a Widget
class CounterView extends ConsumerWidget {
const CounterView({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return ElevatedButton(
onPressed: () => ref.read(counterProvider.notifier).increment(),
child: Text('$count'),
);
}
}
Widgets that read providers extend ConsumerWidget instead of StatelessWidget, which supplies the WidgetRef ref parameter. Same watch/read distinction as Provider: ref.watch() inside build() to rebuild on change, ref.read() inside callbacks to act without subscribing.
The Big Advantage: AsyncValue
For data that loads asynchronously — an API call, a database query — use AsyncNotifier, whose state is automatically wrapped in an AsyncValue that models loading, data, and error states without you having to build that logic yourself:
class UserNotifier extends AsyncNotifier<User> {
@override
Future<User> build() async {
return await repository.fetchUser();
}
}
final userProvider = AsyncNotifierProvider<UserNotifier, User>(UserNotifier.new);
// In the widget:
final userAsync = ref.watch(userProvider);
userAsync.when(
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text('Error: $err'),
data: (user) => Text(user.name),
);
This is the feature Provider has no direct equivalent for — with plain ChangeNotifier, you’d manually track a loading boolean and an error field yourself.
Should You Choose Riverpod Over Provider?
For a new project, Riverpod is generally the better default — it catches missing-provider mistakes at compile time rather than runtime, and the AsyncValue pattern removes a lot of repetitive loading/error boilerplate. Provider’s main advantage is a slightly gentler learning curve if you already know ChangeNotifier from elsewhere in the Flutter SDK.