Migration to Flutter Guide
Discover our battle-tested 21-step framework for a smooth and successful migration to Flutter!
Home
Glossary

Riverpod in Flutter

What is Riverpod in Flutter?

Riverpod is a state management and dependency injection library for Flutter. It is designed to manage application state in a predictable, testable, and scalable way, without relying on the widget tree for dependency resolution.

In modern Flutter app development, Riverpod is used to encapsulate state, business logic, and side effects inside dedicated providers. It supports both synchronous and asynchronous state handling and integrates tightly with Dart's type system, offering strong compile-time guarantees.

Unlike older approaches, Riverpod does not depend on BuildContext to resolve dependencies, which makes state access explicit, testable, and independent of widget hierarchy.

Why does Riverpod matter in Flutter app development?

Riverpod improves testability and modularity by fully decoupling state and business logic from the widget tree. Providers can be tested in isolation without Flutter bindings.

Code generation and modern APIs like Notifier and AsyncNotifier drastically reduce boilerplate compared to earlier Flutter state management approaches. This makes Riverpod especially attractive for teams that value developer experience, fast iteration, and strong typing.

How does Riverpod work?

Modern Riverpod is built around code generation using riverpod_generator. Instead of manually defining providers, developers declare state using simple @riverpod annotations, and the library generates strongly typed and optimized provider code automatically.

This approach significantly reduces boilerplate, improves readability, and eliminates many classes of runtime errors caused by manual provider wiring. Provider lifecycles, dependency tracking, caching, and invalidation are all handled by the generated code.

AsyncNotifier as the core of modern Riverpod

The most important concept in modern Riverpod is AsyncNotifier. It allows developers to manage asynchronous operations such as API calls or database access in a declarative and structured way.

Together with AsyncValue, it enforces explicit handling of all possible states: loading, data, and error. This means the UI layer cannot silently ignore loading or failure scenarios, which dramatically reduces inconsistent UI behavior and null-related runtime issues.

AsyncNotifier centralizes async logic, making it easier to reason about data flow and side effects.

Accessing state and reactivity

State access in Riverpod is performed through the WidgetRef object.

A key best practice is using:

  • ref.watch for reactive UI updates
  • ref.listen for handling side effects such as showing SnackBars, dialogs, or triggering navigation.

This clear separation ensures widgets stay focused on rendering, while side effects remain explicit and controlled.

This distinction is crucial for keeping Flutter UIs predictable and easy to maintain.

Actions and side effects

In modern Riverpod, actions live together with the state they modify.

Methods that change state are defined inside the same class as the state itself, typically a Notifier or AsyncNotifier. This encapsulation keeps business logic cohesive, improves testability, and prevents logic from being scattered across the widget tree.

Riverpod therefore models both data and behavior, not just state storage.

Modern provider example

@riverpod
String myValue(MyValueRef ref) {
  return 'Hello';
}

Riverpod automatically handles provider lifecycle, caching, invalidation, and dependency tracking. The generated code ensures strong typing and compile-time safety.

AsyncNotifier and AsyncValue in practice

AsyncNotifier is used to manage asynchronous state, while AsyncValue represents the current state of that operation.

AsyncValue always exists in one of three explicit states: loading, data, or error. This forces the UI layer to handle every case explicitly instead of assuming data is always available.

In practice, this leads to more resilient UI code and fewer production-only crashes caused by missing error handling.

When to use Riverpod?

Riverpod is an advanced state management solution that provides flexibility and safety.

Clear separation between UI and business logic

Riverpod is especially valuable when an application requires a clear separation between UI and business logic.

It allows developers to colocate:

  • State
  • The logic that modifies the state

This helps maintain a predictable flow of data throughout the app, which is particularly important in larger applications where multiple widgets depend on shared or reactive state.

Asynchronous data handling

Riverpod works well when asynchronous data is a central part of the application.

This includes scenarios such as:

  • Fetching data from APIs
  • Reading from local storage
  • Managing streams

Riverpod’s declarative providers can automatically handle lifecycle, caching, and updates.

For example, a list that depends on multiple API endpoints can be composed from multiple providers so that changes propagate correctly without redundant rebuilds.

Compile-time safety and explicit dependencies

Riverpod is also useful when compile-time safety and explicit dependencies are important.

It enforces clear relationships between state providers, making it easier to understand which part of the UI depends on specific data. This reduces runtime errors and helps teams maintain larger codebases.

Fast iteration with structured architecture

Riverpod supports fast iteration while maintaining a clean architecture.

Developers can quickly add or modify state logic while keeping UI code modular. It is widely used in production applications ranging from small startups to enterprise-grade Flutter projects, offering a balance of maintainability, reactivity, and testability.

When Riverpod can become problematic

Riverpod itself does not introduce hidden dependencies. On the contrary, dependencies are explicit through ref.watch and are often validated at compile time through generated code.

The real risk in large projects comes from unstructured flexibility.

Problems typically appear when:

  • Providers are created without clear architectural boundaries.
  • Business logic is spread across loosely related providers.
  • Feature ownership is unclear in large teams.
  • Everything depends on everything due to lack of conventions.

In these cases, the issue is not hidden coupling, but unstructured coupling — a provider graph that becomes difficult to reason about because no constraints were enforced.

Riverpod gives a lot of freedom. Without discipline, that freedom can lead to spaghetti architecture.

Flutter BLoC vs Riverpod

BLoC enforces a strict and explicit data flow. State changes are driven by events, and transitions are easy to trace. This rigidity scales extremely well in large teams and long-lived commercial projects where predictability and onboarding matter.

Riverpod favors flexibility and developer ergonomics. State, logic, and actions live together, which speeds up development and reduces boilerplate, but requires architectural discipline to avoid disorder.

In practice, Riverpod optimizes for speed and expressiveness, while BLoC optimizes for structural clarity and long-term maintainability.

Best practices

Recommended Riverpod practices include:

  • Always using code generation with @riverpod.
  • Preferring Notifier and AsyncNotifier.
  • Keeping providers small and focused on a single responsibility.
  • Using ref.listen strictly for side effects.
  • Enforcing architectural boundaries at the team level.

ChangeNotifier and StateNotifier should be treated as legacy compatibility tools rather than primary building blocks in modern Riverpod codebases.

Learn more

Flutter architecture by LeanCode

Feature-Based Flutter App Architecture - LeanCode

Read about the LeanCode approach to Flutter architecture. We highlight some of the design decisions that should be made when developing a feature in mobile apps. This includes our approach to dependency injection, state management, widget lifecycle, and data fetching.

12 Flutter & Dart Code Hacks
by LeanCode

12 Flutter & Dart Code Hacks & Best Practices – How to Write Better Code?

In this article, we’re sharing LeanCode’s 12 practical Flutter and Dart patterns that help you write less boilerplate, make your code cleaner, and catch mistakes earlier. Apply these patterns and you'll find yourself coding faster, communicating more clearly with your teammates, and spending less time debugging issues that the compiler could have caught.

UI Testing in Flutter by LeanCode

The Role of UI Testing in Large-Scale Applications

We dive into the topic of UI testing in Flutter. Read more about automated UI tests' benefits and available solutions and why UI testing plays a crucial role, especially in large-scale applications.