Riverpod Documentation

website·Indexed 35 days ago

https://riverpod.dev/

A reactive caching and state management framework for Flutter. The documentation covers core concepts such as providers, families, scoping, and automatic disposal, as well as advanced features like mutations and offline persistence introduced in Riverpod 3.0. It includes migration guides from Provider, ChangeNotifier, and StateNotifier, as well as tutorials on code generation, hooks, and testing providers in isolation.

Tokens
30.9K
Snippets
164
Records
190
Agent score
72%

What's inside Riverpod

  1. Overview of Riverpod offline persistence

    Offline persistence allows the state of Providers to be stored on the user's device for access during offline mode or after app restarts. It acts as a simple wrapper around databases and is not intended to replace full database interaction code (e.g., for advanced migrations or optimized storage strategies). It consists of two main components: a Storage interface to interact with the database and the AnyNotifier.persist function used within notifiers to opt-in to persistence.
  2. Overview of Riverpod code generation

    Code generation in Riverpod is an optional but recommended approach to defining providers. It replaces the manual instantiation of provider types (like FutureProvider or StateNotifierProvider) with annotations, allowing the generator to determine the most suitable provider type based on the return type of the function or class.

    Key benefits include:

    • Simplified Syntax: Providers are defined as functions or classes rather than global variables.
    • Flexible Parameters: Removes the limitation of the .family modifier, allowing named, optional, and default parameters.
    • Developer Experience: Enables stateful hot-reload and provides better debugging metadata.
    • Automatic Type Selection: Riverpod automatically handles loading/error states via AsyncValue when a provider is marked as async.
  3. Understand the role of Hooks in Flutter and Riverpod

    Hooks are utilities from the flutter_hooks package designed as an alternative to StatefulWidget to make local widget logic more reusable and composable. While Riverpod providers are used for "global" application state, hooks are used for local widget state, such as managing TextEditingController or AnimationController. They can also replace the "builder" pattern (e.g., FutureBuilder) to reduce nesting and improve readability. Hooks are particularly helpful for forms, animations, and reacting to user events.
  4. Understand Riverpod default retry behavior

    By default, Riverpod providers are automatically retried when an exception is thrown during computation. The default logic allows up to 10 retries with an exponential backoff ranging from 200ms to 6.4 seconds.

    Crucially, the default logic will NOT retry:

    1. Error objects: These are considered non-recoverable bugs.
    2. ProviderException objects: These indicate that the provider failed because a dependency it watches failed. In this case, only the root failing provider is retried.
  5. Maintain Riverpod code with lint rules and refactors

    Riverpod provides custom lint rules and IDE refactoring options (such as "Wrap widget in a Consumer") to prevent common mistakes and ensure code maintainability. Many of these are implemented via the riverpod_lint package.
  6. Inspect state using Flutter DevTools

    Riverpod includes a dedicated Flutter DevTools extension that allows developers to inspect the current state of all active providers and navigate back in time to inspect previous states.
  7. Understand Riverpod Providers

    Providers in Riverpod are essentially "memoized functions." They wrap a function and cache its result, allowing multiple widgets to access the same value without re-executing the logic (e.g., avoiding redundant network requests).

    Key capabilities of providers include:

    • Cache Invalidation: Using Ref.watch to combine caches and automatically invalidate them.
    • Automatic Disposal: Releasing resources when no longer needed.
    • Data-binding: Eliminating the need for FutureBuilder or StreamBuilder.
    • Error Handling: Automatically catching and exposing errors to the UI.
    • Mocking: Support for provider overrides during testing.
    • Persistence & Mutations: Experimental support for offline disk persistence and built-in state handling for side effects (like form submissions).
  8. Avoid using ref in unmounted widgets after async gaps

    Using ref in a widget that has been unmounted (e.g., after an await call) will throw the error: "Using 'ref' when a widget is about to or has been unmounted is unsafe." (or "Bad state: No ProviderScope found" in older versions).

    To prevent this, check the mounted property of the BuildContext before accessing ref after any asynchronous operation.

    ElevatedButton(
      onPressed: () async {
        await future;
        if (!context.mounted) return;
        ref.read(...); // Safe to use ref here
      }
    )
    
  9. Migrate from ChangeNotifier to AsyncNotifier in Riverpod

    Migrating from ChangeNotifier to AsyncNotifier involves shifting from mutable state and manual listener notification to immutable state managed by Riverpod. This removes the need for manual loading/error flags (like isLoading or hasError) and tedious try/catch/finally blocks, as Riverpod handles asynchronous states via AsyncValue.
  10. Prevent interactions with disposed Refs and Notifiers using Ref.mounted

    In Riverpod 3.0, interacting with a Ref or Notifier after it has been disposed will throw an error. To prevent this, especially after asynchronous gaps (like await), use ref.mounted to verify if the provider is still active before proceeding.
    final provider = FutureProvider<int>((ref) async {
      await Future.delayed(Duration(seconds: 1));
      
      // Abort the provider if it has been disposed during the await.
      if (!ref.mounted) throw MyException();
      
      return 42;
    });
  11. Avoid type collisions with multiple providers of the same type

    In Provider, declaring multiple providers of the same type (e.g., two Provider<Item>) leads to unreliable behavior because the InheritedWidget API only retrieves the closest ancestor. Riverpod eliminates this limitation, allowing developers to split logic into multiple providers of the same type without conflict.
    final itemsProvider = Provider.autoDispose(
      (ref) => <Item>[], // ...
    );
    
    final evenItemsProvider = Provider.autoDispose((ref) {
      final items = ref.watch(itemsProvider);
      return [...items.whereIndexed((index, element) => index.isEven)];
    });
  12. Declare a provider in Riverpod

    Providers are the central feature of Riverpod and act as a description of state. To create a provider, declare a final top-level (global) variable. Providers are immutable and testable.

    Key components of a provider declaration:

    • Provider Variable: A final global variable used to interact with the provider.
    • Provider Type: Determined by the return value of the provider function. Common types include Provider (for synchronous values), FutureProvider (for Future values), and StreamProvider (for Stream values).
    • Modifiers (Optional): Used to tweak behavior. autoDispose clears the cache when the provider is no longer used, and family allows passing arguments to the provider.
    • Ref: An object used to interact with other providers, provided as a parameter to the provider function or as a property of a Notifier.
    • Provider Function: Contains the logic. It is called on the first read and cached for subsequent reads.

    Unlike package:provider, Riverpod allows multiple providers of the same return type.

    final cityProvider = Provider((ref) => 'London');
    final countryProvider = Provider((ref) => 'England');