In Flutter's provider package, which provider class fits a plain object, a ChangeNotifier, a Future and a Stream, and why?
answer
- one class per kind of value
- plain Provider does not listen
- ChangeNotifierProvider listens and disposes
- Future and Stream need initialData
- debug error for Listenable in Provider
basics
~10 sProvider exposes a plain value without listening; ChangeNotifierProvider listens to a ChangeNotifier and disposes it; ListenableProvider and ValueListenableProvider cover other listenables; FutureProvider and StreamProvider expose an async result, starting from a required initialData.
solid answer
~40 sThe provider package has a class per kind of value, because each kind needs a different update mechanism. `Provider<T>` exposes a service or immutable value and never listens; in debug builds it throws a `FlutterError` if the value is a `Listenable` or a `Stream`, since dependents would never update. `ChangeNotifierProvider` subscribes to a `ChangeNotifier`, rebuilds dependents on `notifyListeners`, and disposes a notifier it created. `ListenableProvider` does the same for any `Listenable` but only disposes if you pass `dispose`. `ValueListenableProvider.value` exposes a `ValueListenable`'s current value. `FutureProvider` and `StreamProvider` expose the latest async result and require `initialData`, shown until the first value arrives. In a bakery app: `Provider<BakeryApi>`, `ChangeNotifierProvider<ThemeNotifier>`, `FutureProvider<List<String>>` for today's specials, all in one `MultiProvider`.
code
dart · 58 linesimport 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class BakeryApi {
Future<List<String>> todaysSpecials() async => ['Rye loaf', 'Almond croissant'];
Stream<int> ovenTemperature() =>
Stream.periodic(const Duration(seconds: 5), (i) => 180 + i % 5);
}
class ThemeNotifier extends ChangeNotifier {
ThemeMode _mode = ThemeMode.light;
ThemeMode get mode => _mode;
void toggle() {
_mode = _mode == ThemeMode.light ? ThemeMode.dark : ThemeMode.light;
notifyListeners();
}
}
void main() {
runApp(
MultiProvider(
providers: [
// A service object: exposed, never listened to.
Provider<BakeryApi>(create: (_) => BakeryApi()),
// A ChangeNotifier: listened to and disposed automatically.
ChangeNotifierProvider<ThemeNotifier>(create: (_) => ThemeNotifier()),
// One async result, empty until the Future completes.
FutureProvider<List<String>>(
create: (context) => context.read<BakeryApi>().todaysSpecials(),
initialData: const [],
),
// The latest event of a Stream, null until the first one.
StreamProvider<int?>(
create: (context) => context.read<BakeryApi>().ovenTemperature(),
initialData: null,
),
],
child: const BakeryApp(),
),
);
}
class BakeryApp extends StatelessWidget {
const BakeryApp({super.key});
@override
Widget build(BuildContext context) {
final mode = context.watch<ThemeNotifier>().mode;
final specials = context.watch<List<String>>();
return MaterialApp(
themeMode: mode,
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
home: Scaffold(body: Center(child: Text(specials.join(', ')))),
);
}
}go deeper
Recall the pairing: Provider for plain objects, ChangeNotifierProvider for notifiers, FutureProvider and StreamProvider for async values with initialData, and MultiProvider to list them.
Explain how each class propagates updates, why Provider rejects Listenable and Stream values in debug, and how ListenableProvider's disposal differs from ChangeNotifierProvider's.
Show you pick types deliberately: explicit interface types for testability, distinct types instead of duplicate Strings, and async providers only for simple feeds.
Weigh how far provider's per-kind classes scale for a growing app against a library with richer async handling, and set conventions for which kinds of state belong in which class.
## Why there are several provider classes The **provider** package (version 6 here) wraps `InheritedWidget` so a value created once can be read by any descendant. What differs between kinds of values is **how dependents learn that the value changed**. A plain object never changes; a `ChangeNotifier` calls `notifyListeners`; a `Future` completes once; a `Stream` emits many times. Each provider class wires one of these mechanisms, so choosing the class is choosing how updates propagate. ## The classes and what they do | Class | Exposes | Rebuilds dependents when | Disposes a created value | |---|---|---|---| | `Provider<T>` | any object | the provider itself is rebuilt with a new value | only through its `dispose` callback | | `ChangeNotifierProvider<T>` | a `ChangeNotifier` | `notifyListeners()` is called | yes, calls `dispose()` automatically | | `ListenableProvider<T>` | any `Listenable` | the listenable notifies | only if `dispose:` is passed | | `ValueListenableProvider<T>.value` | `ValueListenable.value` | the value changes | no, it only wraps an existing object | | `FutureProvider<T>` | the future's result | the future completes | not applicable | | `StreamProvider<T>` | the latest event | the stream emits a new, non-equal value | the subscription is cancelled | ## `Provider`: values that do not notify `Provider<T>` is the most basic form. It suits services and immutable values: an API client, a repository, a price list. It does **not** subscribe to anything. For that reason, in debug builds it runs a check and throws a `FlutterError` — *Tried to use Provider with a subtype of Listenable/Stream* — when the value is a `Listenable` or a `Stream`, and suggests `ListenableProvider`, `ChangeNotifierProvider`, `ValueListenableProvider` or `StreamProvider` instead. The check can be switched off globally by setting `Provider.debugCheckInvalidValueType = null`, but it exists because the mistake is common. ## Listenable providers - **`ChangeNotifierProvider`** is the everyday choice for mutable app state, such as a `ThemeNotifier` holding the bakery app's `ThemeMode`. It listens to the notifier and, when it created the notifier through `create`, calls `dispose()` when the provider leaves the tree. - **`ListenableProvider`** is the general version for any `Listenable`, such as an `AnimationController`. Because a generic `Listenable` has no guaranteed `dispose`, it frees resources only when you pass a `dispose` callback. - **`ValueListenableProvider`** exposes the **current value** of a `ValueListenable` rather than the object itself. In provider 6 it has only a `.value` constructor: its default constructor was removed in 5.0 and `.value` was reintroduced shortly after. Internally it is a `ValueListenableBuilder` around `Provider.value`, and its documentation highlights tests, where a `ValueNotifier` can drive the app. ## Async providers `FutureProvider` and `StreamProvider` expose a plain `T`, not a snapshot type. Since provider 5.0 both require **`initialData`**, which descendants see until the first result arrives. They accept an optional `catchError` builder that turns an error into a fallback value. The package's own documentation says `StreamProvider` is for exposing a stream's content to many widgets, such as a battery level, and that replacing a `ChangeNotifier` with streams is out of its scope. ## Combining them with `MultiProvider` Apps usually need several of these at once. `MultiProvider(providers: [...], child: ...)` flattens the nesting; the package states the result is strictly the same widget tree as nesting them by hand. Order matters, because each provider sits **above** the ones listed after it, so a later provider's `create` can read an earlier one. A `child` passed to an individual provider inside the list is ignored. ## Declaring the type explicitly Lookups are **by type**, so the type argument on the provider is part of its contract. Writing `ChangeNotifierProvider<ThemeNotifier>(create: ...)` rather than relying on inference keeps the registered type stable when `create` returns a subclass, and lets a provider expose an interface — `Provider<BakeryApi>(create: (_) => HttpBakeryApi())` — so tests can swap the implementation. Two providers of the same type cannot both be reached from one widget; the nearest one wins, so values that share a Dart type, such as two strings, deserve their own wrapper types. ## Choosing in practice 1. Does the value change after creation? If not, use `Provider`. 2. Is it a `ChangeNotifier` you own? Use `ChangeNotifierProvider`. 3. Is it another `Listenable`, such as an animation or a `ValueNotifier` you want as an object? Use `ListenableProvider`; if you want only its value, `ValueListenableProvider.value`. 4. Is it a single async result, like today's specials? Use `FutureProvider` with an `initialData`. 5. Is it a continuous feed, like oven temperature readings? Use `StreamProvider`. 6. Is it computed from other providers, like a cart total? That is `ProxyProvider`'s job.
- Why does provider throw when a ChangeNotifier is exposed through a plain Provider?Because `Provider` never subscribes, so widgets would read the notifier once and never rebuild when it calls `notifyListeners`. In debug builds `Provider.debugCheckInvalidValueType` throws a `FlutterError` for any `Listenable` or `Stream` value and names the specialised providers to use instead. It can be disabled by setting it to null, but that only hides the mistake.
- How do you expose an implementation while widgets read an interface type?Give the provider an explicit type argument: `ChangeNotifierProvider<CartRepository>(create: (_) => LocalCartRepository())`. Lookups are by type, so descendants reading `CartRepository` find it, and tests can supply a fake with the same declared type. Without the explicit type, inference would register the implementation type and lookups of the interface would fail.
- Can two providers of the same type both be read by one widget?No. A widget obtains only the closest ancestor of a given type, so an inner `Provider<String>` hides an outer one. The package's advice is to give each value its own type, for example `Provider<ShopName>` and `Provider<CityName>`, instead of two providers of `String`.
saying these in an interview costs you the question
- Provider rebuilds dependents whenever the ChangeNotifier it exposes calls notifyListeners.
- ListenableProvider always disposes the Listenable it created.
- FutureProvider exposes an AsyncSnapshot that widgets must unwrap.
- ValueListenableProvider in provider 6 has a create constructor like the others.
- MultiProvider changes behaviour compared with nesting the same providers by hand.