100 Questions to Master Riverpod Foundations
This is Part 6 — the finale of Mastering Riverpod: Foundation. The previous five parts taught the concepts; now you prove you own them.
How to use this bank:
- 100 questions, grouped by topic, tagged [Basic], [Medium], [Advanced] and marked (Theory) or (Coding).
- Each has a Hint and a separate Solution. Try cold first, peek at the hint if stuck, then check the solution.
- For (Coding) questions, write real Riverpod 3.0 code. Targets
flutter_riverpod: ^3.0.0. - After the 100 there are 10 coding mini-exercises with full solutions, ending in a capstone.
If you can explain the why on all 100, you've genuinely mastered Riverpod's foundations. Let's go.
Section A — What is Riverpod & why (Q1–18)
Q1. [Basic] (Theory) In one sentence, what is Riverpod?
Hint
Reactive + caching. Part 1.
Solution
A reactive caching and state-management framework where you declare state as providers and widgets watch them to rebuild on change.
Q2. [Basic] (Theory) What is a provider, conceptually?
Hint
Declaration of state.
Solution
A globally-accessible, declarative piece of state (or computation) that widgets and other providers can watch.
Q3. [Basic] (Theory) What runtime crash does Riverpod prevent that provider allows?
Hint
Not found.
Solution
ProviderNotFoundException — provider's Provider.of<T>(context) looks up the tree at runtime; Riverpod references the provider object directly, so a missing one is a compile error.
Q4. [Basic] (Theory) Does Riverpod need BuildContext to read state?
Hint
No.
Solution
No — providers are top-level objects read via a ref, usable in other providers, pure Dart, and tests.
Q5. [Basic] (Theory) Who created Riverpod, and why does that matter?
Hint
Same as provider.
Solution
The author of the provider package (Remi Rousselet) created Riverpod to fix provider's limitations — so it deliberately addresses those pain points.
Q6. [Medium] (Theory) Give two things Riverpod can do that provider cannot.
Hint
Type count + context.
Solution
(1) Multiple providers of the same type. (2) Read/compose state without BuildContext. (Plus compile-time safety.)
Q7. [Medium] (Theory) How is Riverpod like a spreadsheet?
Hint
Cells recompute.
Solution
Providers form a dependency graph like spreadsheet cells; a derived provider recomputes when an upstream value changes, and watchers update — automatic recalculation.
Q8. [Medium] (Theory) Riverpod vs BLoC — the main trade-off?
Hint
Boilerplate.
Solution
Same testability/separation, but Riverpod needs far less boilerplate than BLoC's events/states/mappers.
Q9. [Medium] (Theory) Riverpod vs GetX — two reasons to prefer Riverpod?
Hint
Compile-safe + testable.
Solution
Riverpod is compile-safe (explicit, checked dependencies, no hidden global magic) and easily testable in isolation, whereas GetX leans on global service-locator magic that's harder to test/reason about.
Q10. [Medium] (Theory) What does "reactive caching" mean for a provider?
Hint
Compute once, reuse.
Solution
A provider computes its value once and caches it, recomputing only when a dependency changes or it's invalidated — and watchers react to those changes.
Q11. [Medium] (Coding) Sketch the smallest provider + the widget reading it.
Hint
Provider + ConsumerWidget.
Solution
final msgProvider = Provider<String>((ref) => 'hi');
class S extends ConsumerWidget {
const S({super.key});
@override Widget build(c, ref) => Text(ref.watch(msgProvider));
}
Q12. [Medium] (Theory) When might you not need Riverpod?
Hint
Local only.
Solution
For tiny apps with purely local state where setState and a couple of constructors suffice — no shared/cached/derived state.
Q13. [Advanced] (Theory) What did Riverpod 3.0 do to Ref?
Hint
Unified.
Solution
Unified it into a single Ref type (removing FutureProviderRef, AutoDisposeRef, etc.), simplifying the API; the old compile-time checks became riverpod_lint rules.
Q14. [Advanced] (Theory) What happened to StateProvider/StateNotifierProvider/ChangeNotifierProvider in 3.0?
Hint
Legacy import.
Solution
They became legacy — moved to a legacy import path and discouraged in favor of the modern Notifier/AsyncNotifier API (not removed, but signal "don't use for new code").
Q15. [Advanced] (Theory) What is auto-retry in Riverpod 3.0?
Hint
Failing providers.
Solution
Providers that fail during initialization automatically retry with exponential backoff by default (configurable per-provider via retry, or globally).
Q16. [Advanced] (Theory) What is ref.mounted for?
Hint
Like context.mounted.
Solution
To check, after an async gap, whether the provider is still alive before using ref — like BuildContext.mounted but for providers (if (!ref.mounted) return;).
Q17. [Advanced] (Theory) Why is "no global mutable state" a feature, despite providers being global?
Hint
Declaration vs state.
Solution
Providers are global declarations, but their state lives in a ProviderContainer you control and dispose. Two containers are isolated, so there's no shared mutable global — enabling clean tests and predictable behavior.
Q18. [Advanced] (Theory) Why does compile-time safety matter more as an app grows?
Hint
Refactor confidence.
Solution
In large apps, runtime lookup errors are easy to introduce and hard to catch; compile-time provider references mean refactors (renames, removals) surface as compile errors immediately, not as crashes in production.
Section B — Setup (Q19–34)
Q19. [Basic] (Theory) Which package for a standard Flutter app?
Hint
flutter_. Part 2.
Solution
flutter_riverpod.
Q20. [Basic] (Theory) Which package for pure Dart (no Flutter)?
Hint
Core.
Solution
riverpod.
Q21. [Basic] (Theory) Which package for Flutter + flutter_hooks?
Hint
hooks_.
Solution
hooks_riverpod.
Q22. [Basic] (Coding) Write the main() that initializes Riverpod for MyApp.
Hint
ProviderScope.
Solution
void main() => runApp(const ProviderScope(child: MyApp()));
Q23. [Basic] (Theory) Where must ProviderScope go?
Hint
Root.
Solution
Wrapping the topmost widget passed to runApp, above any widget that reads a provider.
Q24. [Medium] (Theory) Symptom and fix for a missing ProviderScope?
Hint
"No ProviderScope".
Solution
Reading a provider throws "No ProviderScope found"; fix by wrapping the root widget in ProviderScope.
Q25. [Medium] (Theory) What command adds Riverpod to a Flutter app?
Hint
pub add.
Solution
flutter pub add flutter_riverpod.
Q26. [Medium] (Theory) What does riverpod_lint provide, and what does it run on?
Hint
custom_lint.
Solution
Riverpod-specific static analysis (misused ref.read/watch, missing deps, unhandled async). It runs via the custom_lint plugin.
Q27. [Medium] (Coding) Enable custom_lint in analysis_options.yaml.
Hint
analyzer plugins.
Solution
analyzer:
plugins:
- custom_lint
Q28. [Medium] (Theory) Why is riverpod_lint more than optional polish in 3.0?
Hint
Former compile checks.
Solution
Riverpod 3.0 moved some former compile-time checks into the lint, so it's part of the safety story, not just style.
Q29. [Medium] (Coding) Run the custom lints from the CLI.
Hint
dart run.
Solution
dart run custom_lint
Q30. [Medium] (Theory) Why does importing flutter_riverpod give you core Provider/Ref too?
Hint
Re-export.
Solution
flutter_riverpod depends on and re-exports the core riverpod package, then adds Flutter bindings — one import gives you both.
Q31. [Advanced] (Coding) Write a minimal smoke-test screen confirming setup works.
Hint
Provider + watch.
Solution
final m = Provider<String>((ref) => 'It works!');
class Home extends ConsumerWidget {
const Home({super.key});
@override Widget build(c, ref) => Scaffold(body: Center(child: Text(ref.watch(m))));
}
Q32. [Advanced] (Theory) Are these dev or runtime dependencies: custom_lint, riverpod_lint?
Hint
Tooling.
Solution
Dev dependencies (dev_dependencies) — they're analysis tooling, not shipped in the app.
Q33. [Advanced] (Theory) Could you use both riverpod and flutter_riverpod in one Flutter app?
Hint
Redundant.
Solution
You shouldn't need to — flutter_riverpod re-exports riverpod. Adding both is redundant; just import flutter_riverpod.
Q34. [Advanced] (Theory) Why does ProviderScope need to be above every reader, not just most?
Hint
Container ancestor.
Solution
Reading a provider resolves through the nearest ProviderScope's container; any reader with no ProviderScope ancestor has nowhere to resolve from and throws. Placing it at the root guarantees every reader is covered.
Section C — ProviderScope & ProviderContainer (Q35–54)
Q35. [Basic] (Theory) Does final p = Provider((ref)=>0); create state?
Hint
Declaration. Part 3.
Solution
No — it's a declaration. The 0 lives in a ProviderContainer once read.
Q36. [Basic] (Theory) What does ProviderScope create under the hood?
Hint
Container.
Solution
A ProviderContainer that stores all provider state.
Q37. [Basic] (Theory) Name two jobs of the ProviderContainer.
Hint
Store + dispose.
Solution
Stores provider state and the dependency graph; lazily initializes, caches, and disposes provider state.
Q38. [Basic] (Coding) Read a provider in pure Dart (no widgets).
Hint
ProviderContainer.
Solution
final c = ProviderContainer();
print(c.read(myProvider));
c.dispose();
Q39. [Medium] (Theory) What is the overrides list for?
Hint
Swap providers.
Solution
To replace specific providers with alternatives for that scope — Riverpod's DI and testing mechanism.
Q40. [Medium] (Coding) Override apiProvider with FakeApi() in a test scope.
Hint
overrideWithValue.
Solution
ProviderScope(overrides: [apiProvider.overrideWithValue(FakeApi())], child: MyApp());
Q41. [Medium] (Theory) How does the declaration/container split enable testing?
Hint
Isolated containers.
Solution
State lives in a container you create/dispose, so you can spin up an isolated ProviderContainer, override dependencies, read providers, and assert — no UI, no shared global state.
Q42. [Medium] (Coding) Use the Riverpod 3.0 test helper for an auto-disposing container.
Hint
.test().
Solution
final container = ProviderContainer.test();
Q43. [Medium] (Theory) What do nested ProviderScopes allow?
Hint
Subtree override.
Solution
A child container for a subtree that inherits from above but overrides specific providers just for that subtree.
Q44. [Medium] (Coding) Inject a loaded SharedPreferences to all providers.
Hint
Placeholder + override.
Solution
final prefsProvider = Provider<SharedPreferences>((ref) => throw UnimplementedError());
// main: ProviderScope(overrides: [prefsProvider.overrideWithValue(prefs)], child: MyApp());
Q45. [Medium] (Theory) Why is Provider.overrideWithValue great for tests?
Hint
Transparent fake.
Solution
Widgets keep watching the original provider but transparently receive the fake value, so real screens test against mocks with no code changes.
Q46. [Advanced] (Theory) When is a provider's create function actually run?
Hint
Lazy.
Solution
Lazily, the first time the provider is read/watched in a container — then its result is cached (recomputed on dependency change or invalidation).
Q47. [Advanced] (Theory) Two ProviderContainers read the same provider. Do they share state?
Hint
Per container.
Solution
No — each container holds its own state for the provider. The declaration is shared; the state is per-container. (This isolation is why tests don't leak.)
Q48. [Advanced] (Coding) Override a provider to throw in a test to simulate an error path.
Hint
overrideWith.
Solution
ProviderScope(overrides: [
dataProvider.overrideWith((ref) => throw Exception('boom')),
], child: MyApp());
Q49. [Advanced] (Theory) Why prefer DI via Provider + override over passing dependencies through constructors?
Hint
No prop drilling.
Solution
It avoids threading dependencies through many widget constructors ("prop drilling") and lets you swap implementations centrally (e.g. fakes in tests) without touching consumers.
Q50. [Advanced] (Theory) What's a real use case for nested-scope overrides?
Hint
Per-item state.
Solution
Providing per-item state in a list — each item's subtree overrides an "item" provider with its own value — or giving a section a different theme/config from the same declaration.
Q51. [Advanced] (Theory) Why must you dispose a manually-created ProviderContainer?
Hint
Resource cleanup.
Solution
To release the state and run providers' disposal logic (onDispose, subscriptions, timers). ProviderScope does this automatically; a hand-made container is your responsibility (or use ProviderContainer.test()).
Q52. [Advanced] (Coding) Read a derived provider in a test and assert.
Hint
container.read.
Solution
final c = ProviderContainer.test();
expect(c.read(fullNameProvider), 'Vivek Kumar');
Q53. [Advanced] (Theory) What does overrideWith (vs overrideWithValue) let you do?
Hint
New create fn.
Solution
overrideWith replaces the provider's create function (so the override can itself read other providers / build dynamic state), while overrideWithValue supplies a fixed value.
Q54. [Advanced] (Theory) Why is ProviderScope "the root of everything"?
Hint
Owns the container.
Solution
It owns the ProviderContainer where all provider state lives and resolves; every read flows through it, and overrides defined on it shape what every provider resolves to.
Section D — Provider & ConsumerWidget (Q55–76)
Q55. [Basic] (Theory) What does a plain Provider expose?
Hint
Read-only. Part 4.
Solution
A read-only, cached value computed by its (ref) => ... create function.
Q56. [Basic] (Coding) Declare a Provider<int> returning 42.
Hint
Top-level final.
Solution
final answerProvider = Provider<int>((ref) => 42);
Q57. [Basic] (Theory) How is ConsumerWidget different from StatelessWidget?
Hint
Extra ref.
Solution
Its build gets an extra WidgetRef ref parameter to read providers.
Q58. [Basic] (Coding) Read answerProvider in a ConsumerWidget.
Hint
ref.watch.
Solution
@override Widget build(BuildContext c, WidgetRef ref) => Text('${ref.watch(answerProvider)}');
Q59. [Basic] (Theory) What does ref.watch do in build?
Hint
Subscribe.
Solution
Reads the value and subscribes, rebuilding the widget when it changes.
Q60. [Medium] (Theory) What is Consumer for?
Hint
Narrow rebuilds.
Solution
A builder widget that gives a ref for just part of a tree, so only that part rebuilds when the provider changes — a performance tool.
Q61. [Medium] (Coding) Use Consumer to rebuild only a label, leaving a header static.
Hint
builder.
Solution
Column(children: [
const Header(),
Consumer(builder: (c, ref, _) => Text(ref.watch(labelProvider))),
]);
Q62. [Medium] (Theory) When do you need ConsumerStatefulWidget?
Hint
ref + State.
Solution
When you need both a ref and the classic State lifecycle (initState, controllers, animations).
Q63. [Medium] (Coding) Derive totalProvider from priceProvider and quantityProvider.
Hint
watch both.
Solution
final totalProvider = Provider<int>((ref) =>
ref.watch(priceProvider) * ref.watch(quantityProvider));
Q64. [Medium] (Theory) Inside a provider, why use ref.watch (not read) to depend on another?
Hint
Recompute.
Solution
watch makes the provider recompute when the dependency changes; read would only grab a one-time value and never react.
Q65. [Medium] (Coding) Expose a UserRepository that depends on an ApiClient provider.
Hint
watch the client.
Solution
final apiClientProvider = Provider((ref) => ApiClient());
final userRepoProvider = Provider((ref) => UserRepository(ref.watch(apiClientProvider)));
Q66. [Medium] (Theory) Why is plain Provider ideal for DI?
Hint
Shared, overridable.
Solution
It exposes a single cached instance that everything depends on via ref.watch, and it's overridable for tests — DI without a service locator.
Q67. [Medium] (Theory) When does a Provider's create function run, and how often?
Hint
Lazy + cached.
Solution
Lazily on first read; once, then cached — re-running only if a watched dependency changes or it's invalidated.
Q68. [Medium] (Coding) Show two widgets reading the same provider; confirm they share one value.
Hint
Same provider.
Solution
// Both read the same cached instance from the container:
Widget a(WidgetRef ref) => Text(ref.watch(userRepoProvider).name);
Widget b(WidgetRef ref) => Text(ref.watch(userRepoProvider).id);
Both get the identical shared UserRepository.
Q69. [Advanced] (Theory) Why does narrowing rebuild scope with Consumer improve performance?
Hint
Smaller subtree.
Solution
ref.watch rebuilds the watcher; if the whole widget watches, the whole subtree rebuilds. Wrapping only the dependent part in a Consumer limits rebuilds to that small subtree, leaving expensive siblings untouched (Flutter Part 4).
Q70. [Advanced] (Coding) A derived provider's source changes. Trace what rebuilds.
Hint
Graph propagation.
Solution
If firstNameProvider changes, fullNameProvider (which ref.watches it) recomputes; every widget ref.watching fullNameProvider then rebuilds — the change propagates down the dependency graph.
Q71. [Advanced] (Theory) Why are providers declared as top-level final globals safe despite being global?
Hint
Immutable declaration.
Solution
The global is an immutable declaration with no mutable state; the mutable state lives per-container. So the global can't be a source of shared-mutable-state bugs.
Q72. [Advanced] (Coding) Make a ConsumerStatefulWidget with a controller and a watched provider.
Hint
ConsumerState.
Solution
class S extends ConsumerStatefulWidget { const S({super.key});
@override ConsumerState<S> createState() => _S(); }
class _S extends ConsumerState<S> {
final c = TextEditingController();
@override void dispose(){ c.dispose(); super.dispose(); }
@override Widget build(ctx){ final v = ref.watch(myProvider); return Text('$v'); }
}
Q73. [Advanced] (Theory) Could fullNameProvider use ref.read on its sources instead of watch? Consequence?
Hint
Stale derived.
Solution
It could compile, but the derived value would be computed once and never update when the sources change — a stale derived provider. Use ref.watch for dependencies.
Q74. [Advanced] (Theory) Where does WidgetRef come from, and how does it relate to a provider's Ref?
Hint
Widget vs provider.
Solution
WidgetRef is the ref given to widgets (via ConsumerWidget/Consumer/ConsumerState) to read providers. A provider's create function gets a Ref (unified in 3.0). Both offer watch/read/listen; WidgetRef is the widget-side handle into the same container.
Q75. [Advanced] (Coding) Override a derived provider's source in a test and confirm the derived value changes.
Hint
Override the input.
Solution
final c = ProviderContainer.test(overrides: [
firstNameProvider.overrideWithValue('Ada'),
]);
expect(c.read(fullNameProvider), 'Ada Kumar');
Overriding the input flows through the graph to the derived provider.
Q76. [Advanced] (Theory) Summarize the "core Riverpod loop."
Hint
Declare/watch/render.
Solution
Declare a provider, ref.watch it in a ConsumerWidget, render the value — and the widget rebuilds automatically when the provider changes.
Section E — ref.watch / read / listen (Q77–100)
Q77. [Basic] (Theory) What does ref.watch do?
Hint
Subscribe. Part 5.
Solution
Reads + subscribes; the watcher rebuilds/recomputes on change. Use in build and provider create functions.
Q78. [Basic] (Theory) What does ref.read do?
Hint
Once.
Solution
Reads the current value once with no subscription. Use in callbacks/lifecycle.
Q79. [Basic] (Theory) What does ref.listen do?
Hint
Side effect.
Solution
Runs a side-effect callback when a provider changes (with previous/next), without rebuilding the widget.
Q80. [Basic] (Theory) The three verbs: watch/read/listen map to…?
Hint
reflect/act/react.
Solution
watch = reflect (display), read = act (callbacks), listen = react (side effects).
Q81. [Basic] (Coding) Display counterProvider so it updates.
Hint
watch in build.
Solution
Text('${ref.watch(counterProvider)}')
Q82. [Medium] (Coding) Increment a notifier on button tap.
Hint
read .notifier.
Solution
onPressed: () => ref.read(counterProvider.notifier).increment(),
Q83. [Medium] (Theory) Why read (not watch) in onPressed?
Hint
Act, not subscribe.
Solution
A callback should act, not subscribe; watch in a callback would try to create a subscription each call (wrong, lint-flagged). read grabs the current value/notifier.
Q84. [Medium] (Coding) Fix the stale UI:
Widget build(c, ref) => Text('${ref.read(counterProvider)}');
Hint
watch.
Solution
Widget build(c, ref) => Text('${ref.watch(counterProvider)}');
read doesn't subscribe → frozen UI; watch rebuilds on change.
Q85. [Medium] (Theory) Why does ref.read in build (for a displayed value) cause a bug?
Hint
No subscription.
Solution
No subscription means no rebuild on change; the displayed value goes stale. Displayed values must use watch.
Q86. [Medium] (Coding) Show a snackbar when errorProvider becomes non-null.
Hint
listen.
Solution
ref.listen<String?>(errorProvider, (prev, next) {
if (next != null) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(next)));
});
Q87. [Medium] (Theory) What two arguments does a ref.listen callback receive?
Hint
Transition.
Solution
(previous, next) — letting you react to specific transitions (e.g. loading→error).
Q88. [Medium] (Theory) Where do you register ref.listen?
Hint
build.
Solution
In build (tied to the widget lifecycle); it runs the callback on change without rebuilding.
Q89. [Medium] (Coding) Navigate to /success when checkoutProvider becomes AsyncData.
Hint
listen + is AsyncData.
Solution
ref.listen(checkoutProvider, (prev, next) {
if (next is AsyncData) Navigator.of(context).pushNamed('/success');
});
Q90. [Medium] (Theory) Why must navigation/snackbars not go directly in build?
Hint
build runs often.
Solution
build runs many times unpredictably (Flutter Part 4), so side effects there fire repeatedly/at wrong times. ref.listen runs them only on actual change.
Q91. [Advanced] (Theory) Two rules that prevent most ref bugs?
Hint
display/callback.
Solution
(1) In build, never read a displayed value — use watch. (2) In callbacks, never watch — use read.
Q92. [Advanced] (Theory) What is ref.listenManual for?
Hint
Outside build.
Solution
To listen outside build (e.g. in initState); it returns a subscription you must close yourself, unlike ref.listen which is managed by build.
Q93. [Advanced] (Theory) Riverpod 3.0: what happens to ref.listen listeners when a widget goes off-screen?
Hint
Auto-pause.
Solution
Riverpod auto-pauses them (via TickerMode) so side effects don't fire for invisible widgets; they resume when visible again (pause/resume support).
Q94. [Advanced] (Coding) A screen needs watch + read + listen. Sketch all three together.
Hint
display/act/react.
Solution
final total = ref.watch(cartTotalProvider); // display
ref.listen(checkoutProvider, (p, n) { if (n is AsyncData) go(); }); // react
// onPressed: () => ref.read(cartProvider.notifier).checkout(); // act
Q95. [Advanced] (Theory) Why is calling a notifier method via ref.read correct even though it changes state?
Hint
Caller doesn't subscribe.
Solution
You're invoking an action from a callback; the caller doesn't need to subscribe (it's not displaying the notifier). Widgets that display the resulting state watch the provider separately and rebuild.
Q96. [Advanced] (Theory) Could you use ref.watch inside a provider to depend on another, and ref.read in that provider's methods? Why both?
Hint
Depend vs act.
Solution
Yes: use ref.watch in the build/create to establish a reactive dependency (recompute on change), and ref.read inside notifier methods (event-handler-like actions) to grab a value/notifier without re-subscribing. Same display/act split, inside a provider.
Q97. [Advanced] (Coding) Avoid an infinite rebuild loop — what's wrong with watching a provider you also mutate in build?
Hint
Mutate in build.
Solution
Mutating state during build (e.g. calling a notifier method) while watching it triggers a rebuild, which mutates again → loop. Move mutations to callbacks/ref.listen/lifecycle, never build.
Q98. [Advanced] (Theory) When the displayed value and a side effect both depend on one provider, which methods do you use?
Hint
watch + listen.
Solution
ref.watch to display it (rebuild) and ref.listen to run the side effect on change — they coexist in the same build.
Q99. [Advanced] (Theory) Why might riverpod_lint flag ref.read in your build?
Hint
Likely a bug.
Solution
Because read in build is usually a mistake (stale UI) — the lint nudges you to use watch for displayed values, catching the bug as you type.
Q100. [Advanced] (Theory) Summarize the decision guide in one line.
Hint
Three verbs.
Solution
Display/derive → watch; act in a callback → read; side effect on change → listen.
Coding Mini-Exercises
Ten larger problems combining the Foundation parts. Build and run each.
Exercise 1 — Hello provider. A screen that shows a Provider<String>'s value.
Show solution
final msg = Provider<String>((ref) => 'Hello');
class Home extends ConsumerWidget {
const Home({super.key});
@override
Widget build(BuildContext c, WidgetRef ref) =>
Scaffold(body: Center(child: Text(ref.watch(msg))));
}
Exercise 2 — Derived state. priceProvider, qtyProvider, derived totalProvider, displayed.
Show solution
final priceProvider = Provider((ref) => 10);
final qtyProvider = Provider((ref) => 3);
final totalProvider = Provider((ref) => ref.watch(priceProvider) * ref.watch(qtyProvider));
// build: Text('${ref.watch(totalProvider)}'); // 30
Exercise 3 — DI. Expose a Logger service and a Service that depends on it.
Show solution
final loggerProvider = Provider((ref) => Logger());
final serviceProvider = Provider((ref) => Service(ref.watch(loggerProvider)));
Exercise 4 — Test override. Test that a screen shows a fake repo's data.
Show solution
await tester.pumpWidget(ProviderScope(
overrides: [repoProvider.overrideWithValue(FakeRepo(name: 'Test'))],
child: const MyApp(),
));
expect(find.text('Test'), findsOneWidget);
Exercise 5 — Narrow rebuild. A page with a static header and a live Consumer label.
Show solution
Column(children: [
const ExpensiveHeader(),
Consumer(builder: (c, ref, _) => Text(ref.watch(labelProvider))),
]);
Only the label rebuilds on change.
Exercise 6 — Inject prefs. Wire a loaded SharedPreferences via root override.
Show solution
final prefsProvider = Provider<SharedPreferences>((ref) => throw UnimplementedError());
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final prefs = await SharedPreferences.getInstance();
runApp(ProviderScope(overrides: [prefsProvider.overrideWithValue(prefs)], child: const MyApp()));
}
Exercise 7 — watch + read. A counter displayed with watch, incremented with read (assume a Notifier).
Show solution
Text('${ref.watch(counterProvider)}'), // display
onPressed: () => ref.read(counterProvider.notifier).inc(), // act
Exercise 8 — listen side effect. Show a snackbar on every error transition of formProvider.
Show solution
ref.listen(formProvider, (prev, next) {
if (next is AsyncError) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('${next.error}')));
}
});
Exercise 9 — pure-Dart test. Assert a derived provider in a ProviderContainer.
Show solution
final c = ProviderContainer.test(overrides: [
priceProvider.overrideWithValue(5),
qtyProvider.overrideWithValue(4),
]);
expect(c.read(totalProvider), 20);
Exercise 10 — Capstone: a settings-aware greeting. Combine everything: a nameProvider (DI-injected), a derived greetingProvider, a ConsumerWidget that watches the greeting, a button that (via a notifier read) changes the name, and a listen that snackbars when the name changes. Wire ProviderScope and an override-based test.
Show solution
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// A tiny notifier holding the name (full Notifier coverage is Series 2).
class NameNotifier extends Notifier<String> {
@override
String build() => 'Vivek';
void setName(String n) => state = n;
}
final nameProvider = NotifierProvider<NameNotifier, String>(NameNotifier.new);
// Derived greeting depends on the name.
final greetingProvider = Provider<String>((ref) {
final name = ref.watch(nameProvider);
return 'Hello, $name!';
});
class GreetingScreen extends ConsumerWidget {
const GreetingScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final greeting = ref.watch(greetingProvider); // WATCH: display
ref.listen<String>(nameProvider, (prev, next) { // LISTEN: side effect
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Name changed to $next')),
);
});
return Scaffold(
appBar: AppBar(title: const Text('Greeting')),
body: Center(child: Text(greeting, style: const TextStyle(fontSize: 28))),
floatingActionButton: FloatingActionButton(
onPressed: () => // READ: act
ref.read(nameProvider.notifier).setName('Dash'),
child: const Icon(Icons.edit),
),
);
}
}
void main() => runApp(const ProviderScope(child: MaterialApp(home: GreetingScreen())));
// A pure-Dart-style test of the derived provider with an override:
// final c = ProviderContainer.test(overrides: [nameProvider]); // (override the notifier if needed)
// expect(c.read(greetingProvider), 'Hello, Vivek!');
This single screen exercises the whole Foundation series: ProviderScope at the root (Part 3); a derived Provider and a ConsumerWidget (Part 4); and watch (display), read (act on tap), and listen (snackbar side effect) all in one build (Part 5). The Notifier is a preview of Series 2 — you now have everything you need to dive into provider types.
You made it
Six parts and one hundred questions. You now own Riverpod's foundations:
- Why Riverpod and how it beats Provider/BLoC/GetX (Part 1).
- Correct setup with
ProviderScopeandriverpod_lint(Part 2). - Where state lives —
ProviderScope/ProviderContainerand overrides (Part 3). - Declaring and reading providers with
ConsumerWidget(Part 4). watch/read/listen— display, act, react (Part 5).
Next up is the second series — Mastering Riverpod: Provider Types — where we go one-by-one through every provider: Provider, FutureProvider, StreamProvider, NotifierProvider, AsyncNotifierProvider, StreamNotifierProvider, and the legacy ones to migrate away from.