skip to content

Declared Provider Types

Riverpod declares state as global provider objects: Provider for computed values, FutureProvider and StreamProvider for async data, NotifierProvider for mutable state. Interviewers ask which fits.

part ofFlutter Riverpodoverview, primer and where to startread it →
on this pageshow

explore

questions

5

In Riverpod 3, which provider type fits a computed value, a one-off async load, a live stream, and state the UI changes?

level: juniorimportance: must knowfreq 60%

answer

  1. read-only versus mutable
  2. sync value or AsyncValue
  3. Future once, Stream over time
  4. Notifier classes carry methods
  5. legacy import for the old ones

basics

~10 s

Provider for a synchronous computed or injected value, FutureProvider for data loaded once, StreamProvider for values that keep arriving, and NotifierProvider or AsyncNotifierProvider when the UI must change the state through methods.

solid answer

~40 s

Riverpod 3 splits providers by two questions: is the value synchronous or asynchronous, and can the app change it? `Provider` exposes a synchronous value computed from other providers or an injected object such as a repository; watching it gives `T`. `FutureProvider` runs an async function once (again when a watched dependency changes) and `StreamProvider` subscribes to a stream; both are watched as `AsyncValue<T>`. When the UI needs to change the state, use a class: `NotifierProvider` with a `Notifier<T>` whose `build()` returns the initial state and whose methods assign `state`, or `AsyncNotifierProvider` with an `AsyncNotifier<T>` when the initial state is loaded asynchronously. `StateProvider`, `StateNotifierProvider` and `ChangeNotifierProvider` still exist in Riverpod 3 but only under `legacy.dart`, marked as no longer recommended.

code

dart · 25 lines
dart
import 'package:flutter_riverpod/flutter_riverpod.dart';

final chatRepositoryProvider = Provider<ChatRepository>((ref) => ChatRepository());

final profileProvider = FutureProvider<UserProfile>((ref) {
  return ref.watch(chatRepositoryProvider).fetchProfile();
});

final unreadCountProvider = StreamProvider<int>((ref) {
  return ref.watch(chatRepositoryProvider).watchUnreadCount();
});

enum NotificationFilter { all, mentionsOnly }

final notificationFilterProvider =
    NotifierProvider<NotificationFilterNotifier, NotificationFilter>(
  NotificationFilterNotifier.new,
);

class NotificationFilterNotifier extends Notifier<NotificationFilter> {
  @override
  NotificationFilter build() => NotificationFilter.all;

  void showMentionsOnly() => state = NotificationFilter.mentionsOnly;
}

go deeper

for a junior

Recall the five current types and what watching each returns: T for Provider and NotifierProvider, AsyncValue for the three asynchronous ones.

for a middle

Justify each choice by the two questions - synchronous or asynchronous, read-only or mutable - and explain why mutation lives in notifier classes.

for a senior

Spot when a provider has outgrown its type, such as a FutureProvider that now needs edits, and keep legacy types out of new code.

for a principal

Set team conventions for which provider types are allowed where, so state stays read-only by default and mutations go through named notifier methods.

## Providers are declarations In **Riverpod 3** (flutter_riverpod 3.4 at the time of writing), a provider is a global, immutable declaration of how to obtain a piece of state. The value itself lives in a `ProviderContainer` - in a Flutter app, the one created by `ProviderScope` at the root - and widgets read it through a `WidgetRef`. Choosing the right provider type is mostly answering two questions: 1. Is the value available **synchronously**, or does it come from a `Future` or a `Stream`? 2. Does anything outside the provider need to **change** it, or is it derived from other state? ## The five current types | Type | Declared with | `ref.watch` returns | Changed from outside? | |---|---|---|---| | `Provider` | a function returning `T` | `T` | no - recomputed from dependencies | | `FutureProvider` | a function returning `Future<T>` | `AsyncValue<T>` | no | | `StreamProvider` | a function returning `Stream<T>` | `AsyncValue<T>` | no | | `NotifierProvider` | a `Notifier<T>` class | `T` | yes, via the notifier's methods | | `AsyncNotifierProvider` | an `AsyncNotifier<T>` class | `AsyncValue<T>` | yes, via the notifier's methods | Riverpod also ships `StreamNotifierProvider`, the class-based counterpart of `StreamProvider`, for a stream that must also expose methods. ## Mapping a messaging app A messaging app needs several kinds of state, and each lands on a different type: - **The chat repository** - an object other providers use. `Provider<ChatRepository>`: synchronous, never changed by the UI. - **The display name shown in the header** - derived from the profile. `Provider<String>` that watches the profile provider; it recomputes when the profile changes and, since Riverpod 3.0, notifies listeners only when the new value is not `==` to the previous one. - **The user profile, loaded once** - `FutureProvider<UserProfile>`, or `AsyncNotifierProvider` as soon as the UI must edit it (rename, change avatar). - **The live unread-notifications count** - `StreamProvider<int>` over the repository's stream. - **A notification filter the user toggles** - `NotifierProvider<NotificationFilterNotifier, NotificationFilter>`: synchronous state with methods such as `showMentionsOnly()`. ## Why classes for mutable state The functional providers (`Provider`, `FutureProvider`, `StreamProvider`) have no public way to set their value; they only re-run. When a widget must change state, Riverpod puts the logic in a **notifier class**: - `build()` returns the initial state (and can watch other providers); - methods assign `state`, which notifies listeners; - widgets call those methods through `ref.read(provider.notifier)`. Keeping mutations inside the notifier means every change goes through named methods rather than arbitrary assignments from the UI. ## The legacy types Riverpod 3 moved `StateProvider`, `StateNotifierProvider` and (in flutter_riverpod) `ChangeNotifierProvider` to `package:flutter_riverpod/legacy.dart` (or `package:riverpod/legacy.dart`). They were **not removed**; the move signals that they are no longer recommended. New code uses `Notifier` for what `StateProvider` and `StateNotifierProvider` used to do. ## Quick decision list - Derived or injected, synchronous: `Provider`. - Fetched once, read-only: `FutureProvider`. - Pushed over time, read-only: `StreamProvider`. - Changed by the UI, synchronous: `NotifierProvider`. - Changed by the UI, loaded asynchronously: `AsyncNotifierProvider`.

  • When would you move the profile from a FutureProvider to an AsyncNotifierProvider?
    As soon as the UI must change it - renaming the user or changing the avatar. A `FutureProvider` can only re-run its function; an `AsyncNotifier` keeps the same asynchronous loading in `build()` and adds methods that update `state` after saving.
  • Why not use a StateProvider for the notification filter in new Riverpod 3 code?
    `StateProvider` lives in `legacy.dart` in Riverpod 3 and is no longer recommended. A small `Notifier` gives the same synchronous state with named methods, the same API as every other notifier, and no extra import.

saying these in an interview costs you the question

  • Uses a FutureProvider and tries to assign its value from a button.
  • Believes StateNotifierProvider was deleted in Riverpod 3.
  • Expects ref.watch on a StreamProvider to return the raw int.
  • Picks StateProvider as the default for new mutable state.
  • Puts a repository in a NotifierProvider although nothing ever changes it.
open as a page

In Riverpod 3, how does a Notifier's build() method work, and why must initialization live in build rather than the constructor?

level: middleimportance: must knowfreq 50%

basics

~20 s

build() returns the notifier's initial state and may watch other providers; it re-runs, on the same instance, when one of them changes. The constructor runs before the notifier is attached, so ref and state are unavailable there.

open as a page

In Riverpod 3, when do you choose AsyncNotifierProvider over FutureProvider, and how do an AsyncNotifier's build, state, future and update work?

level: middleimportance: should knowfreq 45%

basics

~20 s

Choose AsyncNotifierProvider when asynchronously loaded state must also be changed, like a user profile the user can rename. build() loads it, state is an AsyncValue, future resolves to the first non-loading value, and update() changes data without passing through loading.

open as a page

With Riverpod 3, how does StreamProvider expose a live unread-notifications count, and what does a widget get from ref.watch on it?

level: middleimportance: should knowfreq 35%

basics

~20 s

StreamProvider subscribes to the stream its function returns and exposes the latest event as AsyncValue<int>: loading until the first event, then data, or error if the stream emits one. All widgets watching it share that subscription.

open as a page

Your Riverpod 2 codebase uses StateProvider and StateNotifierProvider; what does Riverpod 3 do with them, and how do you migrate them to Notifier?

level: seniorimportance: should knowfreq 35%

basics

~10 s

Riverpod 3 keeps StateProvider and StateNotifierProvider but moves them to legacy.dart as discouraged. Migrate each to a Notifier: initial state and watched dependencies go into build(), methods assign state, and callers switch to NotifierProvider.

open as a page