Setting Up Riverpod 3.0
This is Part 2 of Mastering Riverpod: Foundation. In Part 1 we covered why Riverpod. Now let's install it correctly. Setup is short, but there are a couple of choices (which package? what tooling?) that beginners get wrong and pay for later. We'll do it the right way once.
Step 1: Pick the right package
Riverpod ships as three packages. You install exactly one of them depending on your project — picking the wrong one is the most common setup mistake.
| Package | Use it when | What it adds |
| --- | --- | --- |
| flutter_riverpod | a normal Flutter app | ProviderScope, ConsumerWidget, Consumer, WidgetRef |
| hooks_riverpod | a Flutter app that also uses flutter_hooks | everything above + hook integration (HookConsumerWidget) |
| riverpod | pure Dart (CLI, server, package) — no Flutter | core providers only, no widgets |
Rule: building a Flutter app and not using flutter_hooks? Use
flutter_riverpod. That's what this series uses. (flutter_riverpodre-exports the coreriverpodpackage, so you get everything.)
Step 2: Add the dependency
Add flutter_riverpod from your project root:
flutter pub add flutter_riverpod
That updates pubspec.yaml with the latest 3.x:
dependencies:
flutter:
sdk: flutter
flutter_riverpod: ^3.0.0
Then import it where you use providers:
import 'package:flutter_riverpod/flutter_riverpod.dart';
That single import gives you Provider, ConsumerWidget, ProviderScope, ref, and the rest. (We'll add code-generation packages much later in the series; for the Foundation series the manual API needs nothing more.)
Step 3: Wrap your app in ProviderScope
This is the one mandatory line that makes Riverpod work. Wrap your entire app — the thing you pass to runApp — in a ProviderScope:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(
const ProviderScope( // ← REQUIRED: the root of all Riverpod state
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(home: HomeScreen());
}
}
ProviderScope is the container that stores the state of every provider. Without it, any attempt to read a provider throws — so it must sit above every widget that uses Riverpod, which means right at the root, wrapping MyApp. We'll spend all of Part 3 on what ProviderScope really is and does; for now, just remember: ProviderScope at the root, always.
⚠️ The #1 setup error: forgetting
ProviderScope, or putting it below a widget that reads a provider. Symptom: an error like "No ProviderScope found." Fix: ensure it wraps the topmost widget passed torunApp.
Step 4: Add riverpod_lint (highly recommended)
Riverpod is compile-safe, but a dedicated lint catches Riverpod-specific mistakes (using ref.read where you should watch, missing dependencies, forgetting to handle async states, etc.). It runs through custom_lint. Add them as dev dependencies:
flutter pub add custom_lint riverpod_lint --dev
Then enable it in analysis_options.yaml:
analyzer:
plugins:
- custom_lint
Now your IDE and dart analyze will flag common Riverpod anti-patterns as you type. Recall from Part 1 that Riverpod 3.0 even moved some old compile-time checks into this lint — so riverpod_lint isn't optional polish, it's part of the safety story.
# Run the custom lints from the command line:
dart run custom_lint
The complete minimal setup
Putting it together, here's a complete, correct starting point:
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
flutter_riverpod: ^3.0.0
dev_dependencies:
custom_lint: ^0.7.0
riverpod_lint: ^3.0.0
# analysis_options.yaml
analyzer:
plugins:
- custom_lint
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) =>
const MaterialApp(home: Scaffold(body: Center(child: Text('Riverpod ready'))));
}
That's a fully wired Riverpod 3.0 project. Everything in the rest of this series builds on exactly this.
A quick smoke test
To confirm it works end-to-end, declare a trivial provider and show it (full explanation in Part 4):
final messageProvider = Provider<String>((ref) => 'It works!');
class HomeScreen extends ConsumerWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final message = ref.watch(messageProvider);
return Scaffold(body: Center(child: Text(message)));
}
}
If you see "It works!" on screen, your ProviderScope, package, and imports are all correct. If you instead get a "No ProviderScope" error, revisit Step 3.
Practice Challenges
Challenge 1 — Choose the package. Which Riverpod package for: (a) a standard Flutter app, (b) a Dart CLI tool, (c) a Flutter app using flutter_hooks?
Show solution
(a) flutter_riverpod, (b) riverpod (pure Dart, no Flutter widgets), (c) hooks_riverpod (adds hook integration on top of flutter_riverpod).
Challenge 2 — Wire the root. Write the main() that correctly initializes Riverpod for MyApp.
Show solution
void main() {
runApp(const ProviderScope(child: MyApp()));
}
ProviderScope must wrap the topmost widget passed to runApp.
Challenge 3 — Diagnose. An app throws "No ProviderScope found" only on the settings screen, which is pushed as a new route. What's the likely cause? (Trick question.)
Show solution
If ProviderScope wraps the root MyApp, all routes (including pushed ones) are under it, so this shouldn't happen from routing. The likely real cause is that ProviderScope is not at the true root — e.g. it wraps only the home screen instead of the whole MaterialApp/MyApp, so a separately-rooted route falls outside it. Fix: move ProviderScope to wrap the topmost widget in runApp.
Challenge 4 — Tooling. What does adding custom_lint + riverpod_lint get you, and where do you enable it?
Show solution
They add Riverpod-specific static analysis that flags common mistakes (misusing ref.read/watch, missing dependencies, unhandled async states) as you type. Enable via analysis_options.yaml:
analyzer:
plugins:
- custom_lint
Run from the CLI with dart run custom_lint.
Questions to test yourself
Q1 (basic). Which package do you install for a standard Flutter app (no hooks)?
Show answer
flutter_riverpod. (riverpod is for pure Dart; hooks_riverpod is for apps using flutter_hooks.)
Q2 (basic). What single widget must wrap your app for Riverpod to work, and where does it go?
Show answer
ProviderScope — it must wrap the topmost widget passed to runApp (the root), so it sits above every widget that reads a provider.
Q3 (intermediate). What is ProviderScope responsible for?
Show answer
It's the container that stores the state of all providers. Reading any provider requires a ProviderScope ancestor; without it, provider reads fail. (Deep dive in Part 3.)
Q4 (intermediate). Why add riverpod_lint, and how is it enabled?
Show answer
It statically catches Riverpod-specific anti-patterns (wrong ref.read/watch usage, missing dependencies, unhandled async states) — and in Riverpod 3.0 some former compile-time checks now live here. Enable it via custom_lint in analysis_options.yaml under analyzer: plugins: - custom_lint.
Q5 (intermediate). You get "No ProviderScope found" when reading a provider. What's the fix?
Show answer
Ensure ProviderScope wraps the root widget (the one passed to runApp), above any widget that reads a provider. The error means the read happened outside any ProviderScope ancestor.
Q6 (advanced). Why does flutter_riverpod give you the core Provider/Ref API even though those are conceptually in the pure-Dart riverpod package?
Show answer
flutter_riverpod depends on and re-exports the core riverpod package, then layers Flutter-specific pieces (ProviderScope, ConsumerWidget, WidgetRef) on top. So importing flutter_riverpod gives you both the framework-agnostic core (providers, Ref) and the Flutter bindings from a single import — you don't add riverpod separately.
Wrapping up
Correct Riverpod 3.0 setup is four small steps:
- Pick one package:
flutter_riverpod(Flutter app),hooks_riverpod(with hooks), orriverpod(pure Dart). flutter pub add flutter_riverpodand import it.- Wrap the root in
ProviderScope— the single mandatory line; the most common error is omitting it or placing it too low. - Add
custom_lint+riverpod_lintand enable inanalysis_options.yamlfor compile-time safety.
We kept saying "ProviderScope stores all your state" and "deep dive coming." That deep dive is next: Part 3 explains ProviderScope — the root of everything: what it actually holds, how overrides work, and why it's the secret to Riverpod's testability.