← Back to blog
Mastering Riverpod: Foundation · Part 2 of 6
August 3, 20267 min read

Setting Up Riverpod 3.0 in a Flutter Project (The Right Way)

RiverpodFlutterDart

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_riverpod re-exports the core riverpod package, 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 to runApp.


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), or riverpod (pure Dart).
  • flutter pub add flutter_riverpod and 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_lint and enable in analysis_options.yaml for 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.