
When a Flutter app is small, managing state usually feels simple. You can use setState() to update a counter, change a button’s appearance, or display a selected item. But as your application grows, things become more complicated. Data may need to be shared between different screens, API responses need to be handled, and business logic can quickly become mixed with UI code.
This is where Flutter Riverpod becomes useful.
Riverpod is a state-management and data-binding solution for Dart and Flutter. It helps you organise application logic, share data between widgets, handle asynchronous operations, and write code that is easier to test and maintain.
In this Flutter Riverpod tutorial for beginners, we will understand how Riverpod works, install the package, create our first provider, update state with NotifierProvider, and build a small working example. You do not need to know Riverpod already, but basic knowledge of Flutter widgets and Dart classes will help.
What Is Flutter Riverpod?
Flutter Riverpod is a library that allows you to manage and access application state outside the widget that displays it.
Think about a shopping app. The cart count might appear on the home screen, product screen, and checkout page. If every screen maintains its own separate cart state, keeping everything synchronised becomes difficult.
With Riverpod, you can define a shared source of state and let different widgets listen to it. When the state changes, the widgets that depend on it can rebuild with the updated value.
Riverpod can help with:
- Sharing data between different widgets and screens.
- Separating business logic from UI code.
- Managing state that changes after user interactions.
- Handling API requests and asynchronous data.
- Testing application logic without building the entire UI.
- Reusing data and logic across a growing Flutter project.
Riverpod is not a replacement for Flutter’s widget system. You will still use widgets such as Scaffold, Column, TextField, and ListView to build your interface. Riverpod helps manage the data and logic those widgets depend on.
Why Use Riverpod for State Management in Flutter?
Flutter offers several ways to manage state, including setState(), Provider, Bloc, and Riverpod. The right choice depends on the size of your application and how your team prefers to organise code.
Here are some situations where Riverpod can be helpful.
1. Share state across multiple screens
Suppose a user selects dark mode in the settings screen. Other screens need to reflect that choice. Riverpod lets you expose the relevant state and access it wherever it is needed.
2. Keep business logic out of widgets
Instead of placing every operation inside a widget’s build() method, you can put state-changing logic in a notifier. Your UI can then focus on displaying the current state and responding to user actions.
3. Handle asynchronous data
Applications frequently load information from APIs. Riverpod provides patterns for working with loading, success, and error states, helping you avoid manually managing these states in every widget.
4. Make your code easier to test
You can test provider logic independently and override providers when you need to supply test data or mock a dependency.
5. Make larger projects easier to maintain
When the application grows, separating data access, business logic, and UI responsibilities can make changes easier to understand.
Riverpod is not automatically the best solution for every project. For a simple screen with one local value, setState() may be enough. Riverpod becomes more useful when state needs to be shared, reused, tested, or managed across several features.
Flutter Riverpod vs Provider: What Is the Difference?
Provider and Riverpod are related, but they are not the same package or API. Riverpod builds on similar ideas while providing its own provider system and development features.
| Feature | Provider | Riverpod |
|---|---|---|
| Main purpose | Dependency access and state management | State management and data binding |
| Widget integration | Uses widgets such as Consumer and context.watch | Uses ConsumerWidget, WidgetRef, and other Riverpod APIs |
| Access to providers | Commonly through the widget context | Through a provider reference |
| Async data | Often combined with other Flutter patterns | Has dedicated asynchronous provider patterns |
| Testing | Supports dependency overrides and testing patterns | Supports provider overrides and isolated testing |
| Learning curve | Familiar to many Flutter developers | Requires learning Riverpod’s provider and reference concepts |
If you already use Provider, you do not necessarily need to rewrite your entire application immediately. Riverpod’s documentation includes migration guidance for moving from Provider gradually.
For a new project, learn the current Riverpod APIs instead of copying older tutorials without checking which version they use.
How to Install Riverpod in Flutter
Let’s create a small Flutter project and add Riverpod.
Step 1: Create a Flutter project
Open your terminal and run:
flutter create riverpod_demo
cd riverpod_demo
Open the project in VS Code or Android Studio.
Step 2: Install the Riverpod package
Run:
flutter pub add flutter_riverpod
This adds the Flutter integration package to your project. Flutter’s package manager will resolve a compatible version for your environment.
You can check the installed dependency in your pubspec.yaml file.
For this basic example, we do not need code generation or additional Riverpod packages.
Step 3: Add ProviderScope to Your Flutter App
Before using Riverpod providers in the widget tree, wrap your application with ProviderScope.
Open lib/main.dart and add the following code:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(
const ProviderScope(
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: Text('Flutter Riverpod Tutorial'),
),
),
);
}
}
ProviderScope creates the provider container used by Riverpod to store and access provider state. Without it, the usual Flutter Riverpod widget integration will not have the required provider scope.
At this stage, we have only prepared the application. Next, let’s create a provider and use it to manage state.
How to Create Your First Riverpod Provider
A provider is a way to expose a value or a piece of logic so that other parts of your application can access it.
For example, you might have a provider that returns the app’s API client, the current user, a calculated value, or a counter’s state.
Riverpod offers different provider types for different needs. In this tutorial, we will use NotifierProvider because it is suitable for state that changes in response to user actions.
Flutter Riverpod Example Using NotifierProvider
We will build a simple counter app. The screen will show a number, and the user can press a button to increase it.
This example demonstrates the basic flow:
- Define the state and its update logic.
- Expose the state using
NotifierProvider. - Read the state from a Flutter widget.
- Update the state when the user taps a button.
Step 1: Create the counter notifier
Add this code above your MyApp class in lib/main.dart:
class Counter extends Notifier<int> {
@override
int build() => 0;
void increment() {
state++;
}
void decrement() {
state--;
}
}
final counterProvider =
NotifierProvider<Counter, int>(Counter.new);
Let’s understand the important parts.
CounterextendsNotifier<int>, which means its state is an integer.build()provides the initial state, which is0.increment()increases the state by one.decrement()decreases the state by one.counterProviderexposes the notifier and its state to other parts of the app.
The state property belongs to the notifier. When you update it, Riverpod can notify widgets that are listening to the provider.
Step 2: Read the state in a ConsumerWidget
Now replace the MyApp class with this code:
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const ProviderScope(
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: CounterPage(),
),
);
}
}
class CounterPage extends ConsumerWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Riverpod Counter'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Current count'),
const SizedBox(height: 8),
Text(
'$count',
style: const TextStyle(
fontSize: 36,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
onPressed: () {
ref.read(counterProvider.notifier).decrement();
},
icon: const Icon(Icons.remove),
),
IconButton(
onPressed: () {
ref.read(counterProvider.notifier).increment();
},
icon: const Icon(Icons.add),
),
],
),
],
),
),
);
}
}
The complete file should contain the imports, the main() function, the Counter notifier, the provider declaration, and the two widget classes. Make sure you have only one main() function and one MyApp class.
Step 3: Run the app
Use the following command:
flutter run
The app should display a counter starting at zero. Press the plus icon to increase the number or the minus icon to decrease it.
You now have a working Flutter Riverpod example.
Understanding ref.watch and ref.read in Riverpod
Two of the most important Riverpod concepts are ref.watch and ref.read.
What does ref.watch do?
final count = ref.watch(counterProvider);
ref.watch listens to a provider’s value. When the value changes, the widget or provider using it can update accordingly.
In our counter app, CounterPage watches counterProvider. Every time the counter changes, the displayed number is rebuilt with the new value.
Use ref.watch when the UI needs to react to changes in a provider.
What does ref.read do?
ref.read(counterProvider.notifier).increment();
ref.read accesses a provider without subscribing to its future changes. Here, it obtains the notifier so we can call its increment() method.
This is appropriate for an action such as a button tap, where we want to trigger an operation rather than listen to the notifier object.
A useful rule for beginners is:
- Use
ref.watchto react to state changes. - Use
ref.readto perform an action, such as calling a notifier method.
Riverpod also provides ref.listen for reacting to changes with side effects, such as showing a message. Use the appropriate method for the task rather than replacing all provider access with one method.
Important Riverpod Provider Types
Riverpod offers different provider types because not all application data behaves in the same way.
Provider
Use Provider to expose a value or a service that other providers or widgets can access. It is useful for dependencies, computed values, and reusable objects.
FutureProvider
Use FutureProvider when you need to expose the result of an asynchronous operation, such as fetching data from an API.
StreamProvider
Use StreamProvider when the data arrives over time through a Dart stream, such as a live data feed.
NotifierProvider
Use NotifierProvider for state that changes in response to user interactions or other events. Our counter example uses this provider type.
AsyncNotifierProvider
Use AsyncNotifierProvider when state needs asynchronous initialisation and may change after user actions. It is a useful option for features such as loading and refreshing remote data.
Choosing the right provider depends on the type of state you are managing. You do not need to learn every provider type on the first day; begin with the basic concepts and move to asynchronous examples as your project requires them.
How to Use Riverpod for API Calls in Flutter
A common use case for Riverpod is fetching data from an API and displaying it in a screen.
Suppose your app needs to retrieve a list of products from a server. You need to consider at least three UI states:
- Loading: the request is still running.
- Success: the server returned the expected data.
- Error: the request failed or returned an unexpected response.
Riverpod supports asynchronous providers that expose loading, data, and error states. The official documentation includes a complete tutorial that builds an app using an API request and handles these states in the UI.
For your own app, you can structure the feature around a repository or service that fetches the data, an asynchronous provider that exposes the result, and a consumer widget that renders the current state.
This separation keeps network code out of the widget and makes the feature easier to test.
For a working network example, follow the official Riverpod first-app tutorial. It demonstrates installation, a provider, an API request, and handling asynchronous results.
Common Mistakes When Using Flutter Riverpod
Riverpod is straightforward once its core concepts make sense, but beginners often run into a few predictable problems.
1. Forgetting ProviderScope
If the app tries to use a provider without the required scope, it may throw an error.
Solution: Wrap the app with ProviderScope near the root of the widget tree.
2. Using ref.watch inside the wrong place
ref.watch is designed for reactive provider access. Using it indiscriminately for button actions or side effects can make code harder to understand.
Solution: Watch values that the UI depends on, and use ref.read for actions such as calling a notifier method.
3. Mixing business logic with UI code
Placing all application logic inside a large widget makes it difficult to maintain and test.
Solution: Move state-changing operations into a notifier or another suitable class, leaving the widget responsible for displaying the state and handling user interactions.
4. Following outdated tutorials
Riverpod has evolved, and older articles may use APIs or patterns that are no longer the preferred approach for new code.
Solution: Check the official documentation for your installed version. For new state that changes in response to user actions, current guidance generally favours NotifierProvider or AsyncNotifierProvider over legacy approaches.
5. Not testing state changes
A screen may look correct initially but fail after repeated taps, an API error, or a change in input.
Solution: Test the notifier’s behaviour and verify how the UI responds to both normal and error cases.
Best Practices for Flutter Riverpod
If you are planning to use Riverpod in a real project, keep these practices in mind:
- Keep provider names clear and descriptive.
- Separate API calls and business logic from UI widgets.
- Use the provider type that matches your data and update requirements.
- Avoid unnecessary dependencies and overly complicated state structures.
- Test important state transitions and error cases.
- Check the current Riverpod documentation before adopting an unfamiliar API.
- Introduce code generation only when it helps your project’s structure and workflow.
You do not need a complicated architecture for every screen. Start with a simple structure and introduce additional layers when they solve a real maintenance or testing problem.
Frequently Asked Questions About Flutter Riverpod
What is Riverpod in Flutter used for?
Riverpod is used for state management and data binding in Flutter and Dart applications. It helps share state, separate logic from UI code, handle asynchronous data, and make application logic easier to test.
Is Riverpod better than Provider in Flutter?
It depends on the project. Riverpod offers its own provider system, testing features, and asynchronous data patterns. Provider is also a valid option and may be suitable for existing applications. Compare the APIs and migration cost before choosing.
Is Flutter Riverpod suitable for beginners?
Yes. Beginners can start with a small example, such as the counter app in this tutorial. Learn ProviderScope, providers, ref.watch, ref.read, and notifiers before moving on to API calls and more complex application state.
What is NotifierProvider in Riverpod?
NotifierProvider exposes a Notifier and its state. It is useful when the state changes through methods or actions, such as adding an item to a cart, updating a counter, or changing a filter.
Can Riverpod handle API calls?
Yes. Riverpod supports asynchronous providers that expose loading, success, and error states. You can use these patterns with your API service or repository to manage remote data in a Flutter application.
Do I need code generation to use Riverpod?
No. You can start with the basic flutter_riverpod package and define providers directly in Dart. Code generation is optional and can be useful in larger projects or when you prefer Riverpod’s annotation-based approach.
Can I use Riverpod with an existing Flutter app?
Yes. You can introduce Riverpod gradually. Start with a feature that benefits from shared state or better separation of logic, and migrate other parts when it makes sense for your project.
Conclusion
Flutter Riverpod can make state management easier to organise as your application grows. It lets you expose shared state, keep business logic separate from widgets, and manage asynchronous operations using dedicated provider patterns.
The best way to learn Riverpod is to start small. Build the counter example, understand how ref.watch and ref.read work, and then try a feature that fetches data from an API.
Once these fundamentals are clear, you can apply the same ideas to real-world features such as authentication, shopping carts, search screens, settings, and dashboards.
For the latest APIs and additional examples, keep the official Riverpod documentation bookmarked while working on your Flutter projects.
Thank you for visiting us, see more flutter Topics.
