Dependency Graph
A dependency graph is the network of relationships between providers, showing how they depend on and react to one another.
What is it?
One of Riverpod's core features is its ability to automatically track dependencies between providers.
Whenever one provider reads another provider using ref.watch(), Riverpod records that relationship.
For example:
userProvider
│
▼
profileProvider
│
▼
HomeScreen
Here:
profileProviderdepends onuserProvider.HomeScreendepends onprofileProvider.
Together, these relationships form the dependency graph.
Riverpod uses this graph to know exactly which providers and widgets need updating when data changes.
Why does it exist?
Imagine manually updating every dependent object whenever state changes.
User changes
│
▼
Update Profile
│
▼
Update Orders
│
▼
Update Dashboard
│
▼
Update Settings
This quickly becomes difficult to maintain.
Instead, Riverpod automatically builds a dependency graph.
Benefits include:
- Automatic updates
- No manual dependency tracking
- Efficient recomputation
- Minimal widget rebuilds
- Better scalability
- Predictable data flow
Syntax
Riverpod builds the dependency graph automatically through ref.watch().
A Simple Dependency
final firstNameProvider = Provider((ref) => 'John');
final fullNameProvider = Provider((ref) {
final firstName = ref.watch(firstNameProvider);
return '$firstName Doe';
});
Explanation:
fullNameProviderdepends onfirstNameProvider.ref.watch()creates the dependency.- If
firstNameProviderchanges,fullNameProvideris recomputed.
Multiple Dependencies
final firstNameProvider = Provider((ref) => 'John');
final lastNameProvider = Provider((ref) => 'Doe');
final fullNameProvider = Provider((ref) {
final firstName = ref.watch(firstNameProvider);
final lastName = ref.watch(lastNameProvider);
return '$firstName $lastName';
});
Explanation:
fullNameProviderdepends on two providers.- Changes to either provider trigger recomputation.
Chained Dependencies
final userProvider = Provider((ref) => User());
final profileProvider = Provider((ref) {
return Profile(ref.watch(userProvider));
});
final dashboardProvider = Provider((ref) {
return Dashboard(ref.watch(profileProvider));
});
Explanation:
- Each provider depends on the previous one.
- Riverpod updates only the affected providers.
Mental Model
Think of providers as nodes in a graph.
userProvider
│
┌────────┴────────┐
▼ ▼
profileProvider orderProvider
│ │
└────────┬────────┘
▼
dashboardProvider
│
▼
HomeScreen
When userProvider changes:
profileProviderupdates.orderProviderupdates.dashboardProviderupdates.- Only widgets watching
dashboardProviderrebuild.
Riverpod automatically follows the graph.
How Riverpod Tracks Dependencies
Whenever a provider executes:
- Riverpod starts recording dependencies.
- Every
ref.watch()call is registered. - The provider becomes a child of those dependencies.
- Future updates use this graph to trigger recomputations.
Example:
final greetingProvider = Provider((ref) {
final language = ref.watch(languageProvider);
return language == 'en'
? 'Hello'
: 'Hola';
});
Riverpod records:
languageProvider
│
▼
greetingProvider
Now any change to languageProvider automatically updates greetingProvider.
Examples
Computed Provider
final priceProvider = Provider((ref) => 100);
final taxProvider = Provider((ref) => 18);
final totalProvider = Provider((ref) {
final price = ref.watch(priceProvider);
final tax = ref.watch(taxProvider);
return price + tax;
});
Explanation:
totalProviderdepends on both providers.- Changes to either value recompute the total.
Repository Dependency
final apiProvider = Provider((ref) {
return ApiService();
});
final repositoryProvider = Provider((ref) {
return UserRepository(
ref.watch(apiProvider),
);
});
Explanation:
- The repository depends on the API service.
- If the API provider changes (for example, through an override), the repository is recreated.
UI Dependency
class HomePage extends ConsumerWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final profile = ref.watch(profileProvider);
return Text(profile.name);
}
}
Explanation:
- The widget depends on
profileProvider. - It rebuilds only when
profileProviderchanges.
Dependency Graph vs Widget Tree
A common misconception is that providers follow the widget tree.
They do not.
Widget Tree
MaterialApp
│
▼
HomePage
│
▼
ProfilePage
This represents the UI hierarchy.
Dependency Graph
userProvider
│
▼
profileProvider
│
▼
dashboardProvider
This represents data relationships.
The two structures are independent.
When to Use
The dependency graph is built automatically whenever you:
- Use
ref.watch() - Compose providers
- Build computed values
- Share repositories
- Combine multiple pieces of state
You don't need to create or manage it manually.
When NOT to Use
Avoid using ref.read() when you need a reactive dependency.
Example:
❌
final fullNameProvider = Provider((ref) {
final firstName = ref.read(firstNameProvider);
return '$firstName Doe';
});
Why it's wrong:
ref.read()does not create a dependency.fullNameProviderwill not update iffirstNameProviderchanges.
✔ Correct
final fullNameProvider = Provider((ref) {
final firstName = ref.watch(firstNameProvider);
return '$firstName Doe';
});
Best Practices
- Use
ref.watch()for reactive dependencies. - Keep dependency chains simple and focused.
- Compose providers instead of creating large providers.
- Avoid circular dependencies.
- Prefer many small providers over one large provider.
Common Mistakes
1. Using ref.read() Instead of ref.watch()
❌ Wrong
final totalProvider = Provider((ref) {
final price = ref.read(priceProvider);
return price + 10;
});
Why it's wrong:
- Changes to
priceProviderare ignored.
✔ Correct
final totalProvider = Provider((ref) {
final price = ref.watch(priceProvider);
return price + 10;
});
2. Circular Dependencies
❌ Wrong
final providerA = Provider((ref) {
return ref.watch(providerB);
});
final providerB = Provider((ref) {
return ref.watch(providerA);
});
Why it's wrong:
- Creates an infinite dependency cycle.
- Riverpod throws an error.
✔ Correct
Restructure providers so dependencies flow in one direction.
3. One Giant Provider
❌ Wrong
AppProvider
├── Authentication
├── Users
├── Orders
├── Products
├── Settings
Why it's wrong:
- Hard to maintain.
- Causes unnecessary recomputation.
✔ Correct
authProvider
userProvider
orderProvider
settingsProvider
Compose them where needed.
Related APIs
- Provider
- ref.watch()
- ref.read()
- ref.listen()
- ProviderContainer
- State Invalidation
- Caching
- select()
Summary
Riverpod automatically builds a dependency graph whenever providers use ref.watch(). This graph allows Riverpod to track relationships between providers, efficiently recompute only affected providers, and rebuild only the widgets that depend on updated data. By leveraging the dependency graph, Riverpod delivers predictable, reactive, and highly optimized state management.