Skip to content

Riverpod

Riverpod 是 Provider 的「升级版」,由 Provider 的同一作者开发。它修复了 Provider 的设计缺陷,提供了更强的类型安全和更灵活的状态管理能力。

Riverpod = River + Pod,是 Provider 的字母重排,意思是「更好的 Provider」。

为什么要学 Riverpod?

Provider 的问题Riverpod 的改进
依赖 BuildContext,测试不方便不依赖 BuildContext,随处可用
运行时才能发现错误编译时就能检查出错误
多个 Provider 同类型会冲突通过「名称」区分,同类型没问题
状态不能在 Widget 树外使用状态是全局的,哪里都能用

适合谁?

建议先学 setStateProvider,再来看 Riverpod。Riverpod 概念稍多,但确实是更现代、更安全的方案。

版本说明

本文基于 flutter_riverpod ^3.3.1 编写。Riverpod 经历了多次重大更新:

版本说明
1.x早期版本,StateProviderStateNotifierProvider 是主力
2.x引入 NotifierProviderStateProvider 标记为废弃
3.x(当前)StateProvider 从主库移除,推荐 NotifierProvider / AsyncNotifierProvider

新旧 API 不要混用

新项目请直接使用新版 API;旧项目如需维护,参考文末 Legacy API 章节。

安装与配置

1. 安装 flutter_riverpod

bash
flutter pub add flutter_riverpod

如果你使用 hooks_riverpod(配合 flutter_hooks 使用):

bash
flutter pub add hooks_riverpod

本文以 flutter_riverpod 为例。

2. 配置 riverpod_lint(推荐)

riverpod_lint 是 Riverpod 官方的 lint 插件,能在编写代码时实时检测常见错误——比如在按钮回调中误用 ref.watch() 而非 ref.read(),编写时直接标红提示。

不了解 analysis_options.yaml?先看 项目结构 → analysis_options.yaml 详解

打开项目根目录的 analysis_options.yaml,加入插件配置:

yaml
include: package:flutter_lints/flutter.yaml

plugins:
  riverpod_lint: 3.1.3

注意

  • pluginsinclude同级的,不要缩进到 include 里面
  • 版本号需与 flutter_riverpod 匹配,查看 pub.dev 获取最新版本

三个核心概念

在 Riverpod 中,你只需要理解三样东西:

概念作用类比
Provider定义一个状态(数据 + 计算逻辑)变量的「定义」
ref读取或监听其他 Provider变量的「引用」
ProviderScope在顶层包裹整个 App状态的「容器」

快速上手:计数器

第一步:包裹 ProviderScope

main() 中用 ProviderScope 包裹你的 App:

dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() {
  runApp(
    // ─── ★ ProviderScope Riverpod 根容器 ──────────────
    const ProviderScope(
      child: MyApp(),
    ),
    // ─── ☆ ProviderScope Riverpod 根容器 ──────────────
  );
}

第二步:创建 Provider

dart
// 定义 Notifier 类(Riverpod 3.x 推荐写法)
class CounterNotifier extends Notifier<int> {
  @override
  int build() => 0;  // 初始值

  void increment() => state++;
}

// 声明 Provider 变量
final counterProvider = NotifierProvider<CounterNotifier, int>(
  CounterNotifier.new,
);

第三步:在 Widget 中使用

ConsumerWidget 替代 StatelessWidget,它的 build 方法多一个 ref 参数:

dart
class CounterPage extends ConsumerWidget {
  const CounterPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);  // 监听变化,值变了自动重建

    return Scaffold(
      appBar: AppBar(title: const Text('Riverpod 计数器')),
      body: Center(
        child: Text('计数: $count', style: const TextStyle(fontSize: 48)),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => ref.read(counterProvider.notifier).increment(),  // 只读一次,不监听
        child: const Icon(Icons.add),
      ),
    );
  }
}

核心规则:watch vs read

这是 Riverpod 最重要的规则,初学者经常搞混:

场景用什么原因
build显示数据ref.watch()数据变了需要重建 UI
在按钮点击中修改数据ref.read()不需要重建按钮本身
在事件回调中读取一次ref.read()只需要当前值
dart
// ✅ 正确
Widget build(BuildContext context, WidgetRef ref) {
  final count = ref.watch(counterProvider);        // 显示数据 → watch
  return ElevatedButton(
    onPressed: () => ref.read(counterProvider.notifier).increment(),  // 修改数据 → read
    child: Text('$count'),
  );
}

// ❌ 错误:在事件回调中用 watch
onPressed: () {
  ref.watch(counterProvider);  // 不要在 build 之外用 watch!
}

一句话记忆

watch 用于「看」,read 用于「做」。配置 riverpod_lint 后,误用会被实时标红提示。

ConsumerWidget vs ConsumerStatefulWidget

Riverpod Widget对应 Flutter Widget何时使用
ConsumerWidgetStatelessWidget只需要读取共享状态
ConsumerStatefulWidgetStatefulWidget既有共享状态,又有自己的内部状态(如输入框文本)

ConsumerStatefulWidgetState 类中可以直接用 ref,也可以用 setState 管理本地状态:

dart
class MyPage extends ConsumerStatefulWidget {
  const MyPage({super.key});
  @override
  ConsumerState<MyPage> createState() => _MyPageState();
}

class _MyPageState extends ConsumerState<MyPage> {
  final _controller = TextEditingController();  // 本地状态

  @override
  Widget build(BuildContext context) {
    final cart = ref.watch(cartProvider);  // 共享状态
    return Text('商品数: ${cart.length}');
  }
}

Provider 类型速查

Provider 类型适用场景典型用例
Provider只读值、计算值主题配置、格式化数据
NotifierProvider同步状态 + 业务逻辑计数器、购物车
AsyncNotifierProvider异步初始化的状态 + 业务逻辑从本地存储加载配置
FutureProvider一次性异步数据从 API 获取数据
StreamProvider持续产生的数据流WebSocket、计时器

下面逐一讲解。

Provider — 只读值与计算值

最简单的 Provider,用于提供不会自己变化的值,或从其他 Provider 派生计算结果:

dart
// 提供一个固定配置
final themeColorProvider = Provider<Color>((ref) => Colors.blue);

// 从其他 Provider 派生
final uncompletedCountProvider = Provider<int>((ref) {
  final todos = ref.watch(todoProvider);
  return todos.where((t) => !t.done).length;
});

Provider 没有修改状态的方法,只负责「给值」。需要修改状态请用 NotifierProvider

NotifierProvider — 同步状态管理

当状态有业务逻辑(如增删改),使用 NotifierProvider——Riverpod 3.x 的核心推荐写法。

创建与使用

dart
// 商品模型
class Product {
  final String name;
  final int price;
  const Product({required this.name, required this.price});
}

// 购物车 Notifier
class CartNotifier extends Notifier<List<Product>> {
  @override
  List<Product> build() => [];  // 初始状态:空购物车

  void add(Product product) => state = [...state, product];
  void remove(int index) => state = [...state]..removeAt(index);
  void clear() => state = [];
}

// 创建 Provider
final cartProvider = NotifierProvider<CartNotifier, List<Product>>(
  CartNotifier.new,
);
dart
// 在 Widget 中使用
class CartPage extends ConsumerWidget {
  const CartPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final cart = ref.watch(cartProvider);
    final totalPrice = cart.fold<int>(0, (sum, item) => sum + item.price);

    return Scaffold(
      appBar: AppBar(title: Text('购物车 (${cart.length})')),
      body: Column(
        children: [
          Expanded(
            child: ListView.builder(
              itemCount: cart.length,
              itemBuilder: (context, index) {
                final item = cart[index];
                return ListTile(
                  title: Text(item.name),
                  trailing: Row(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Text(${item.price}'),
                      IconButton(
                        icon: const Icon(Icons.delete),
                        onPressed: () => ref.read(cartProvider.notifier).remove(index),
                      ),
                    ],
                  ),
                );
              },
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(16),
            child: Text('总计: ¥$totalPrice',
              style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => ref.read(cartProvider.notifier).add(const Product(name: '苹果', price: 5)),
        child: const Icon(Icons.add),
      ),
    );
  }
}

不可变更新

在 Notifier 中更新状态时,必须创建新对象(state = [...state, item]),而不是直接修改(state.add(item) ❌)。直接修改不会触发 UI 刷新。

AsyncNotifierProvider — 异步初始化状态

状态的初始值需要异步获取(如从本地存储读取配置),使用 AsyncNotifierProvider

对比NotifierProviderAsyncNotifierProvider
初始化build() 返回同步值build() 返回 Future
读取状态直接拿到值拿到 AsyncValue,需用 .when() 处理
dart
class SettingsNotifier extends AsyncNotifier<ThemeMode> {
  @override
  Future<ThemeMode> build() async {
    await Future.delayed(const Duration(seconds: 1));  // 模拟异步读取
    return ThemeMode.system;
  }

  Future<void> setTheme(ThemeMode mode) async {
    await Future.delayed(const Duration(milliseconds: 200));  // 模拟异步保存
    state = AsyncData(mode);
  }
}

final settingsProvider = AsyncNotifierProvider<SettingsNotifier, ThemeMode>(
  SettingsNotifier.new,
);
dart
// 在 Widget 中使用 AsyncValue.when() 处理三种状态
class SettingsPage extends ConsumerWidget {
  const SettingsPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final settingsState = ref.watch(settingsProvider);

    return Scaffold(
      appBar: AppBar(title: const Text('设置')),
      body: settingsState.when(
        loading: () => const Center(child: CircularProgressIndicator()),
        error: (err, _) => Text('加载失败: $err'),
        data: (mode) => SwitchListTile(
          title: const Text('深色模式'),
          value: mode == ThemeMode.dark,
          onChanged: (isDark) {
            ref.read(settingsProvider.notifier).setTheme(
              isDark ? ThemeMode.dark : ThemeMode.light,
            );
          },
        ),
      ),
    );
  }
}

FutureProvider — 一次性异步数据

适用于一次请求、加载完成后不再变化的场景,如从 API 获取数据。

dart
Future<String> fetchUserName() async {
  await Future.delayed(const Duration(seconds: 2));
  return '张三';
}

final userNameProvider = FutureProvider<String>((ref) async {
  return fetchUserName();
});

Widget 中同样通过 AsyncValue.when() 处理加载/错误/数据三种状态:

dart
class UserPage extends ConsumerWidget {
  const UserPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final userName = ref.watch(userNameProvider);

    return userName.when(
      loading: () => const Center(child: CircularProgressIndicator()),
      error: (err, _) => Center(child: Text('出错了: $err')),
      data: (name) => Center(child: Text('欢迎, $name', style: const TextStyle(fontSize: 32))),
    );
  }
}

刷新数据用 ref.invalidate()

dart
TextButton(
  onPressed: () => ref.invalidate(userNameProvider),
  child: const Text('刷新'),
);

StreamProvider — 持续数据流

适用于数据会持续产生的场景,如 WebSocket、计时器、传感器数据。

dart
final tickingSecondsProvider = StreamProvider.autoDispose<int>((ref) async* {
  var value = 0;
  while (true) {
    await Future.delayed(const Duration(seconds: 1));
    value++;
    yield value;
  }
});
dart
final secondsState = ref.watch(tickingSecondsProvider);

secondsState.when(
  loading: () => const Text('秒表启动中...'),
  error: (error, _) => Text('错误:$error'),
  data: (value) => Text('已经过 $value 秒'),
);

修饰符:autoDispose 与 family

autoDispose — 自动释放

当没有 Widget 监听时,自动销毁 Provider 并释放资源。就像教室的灯:有人时亮着,所有人走了自动关灯省电。

dart
final searchProvider = FutureProvider.autoDispose<List<String>>((ref) async {
  final query = ref.watch(queryProvider);
  return apiSearch(query);  // 页面离开后自动取消
});

推荐:网络请求、Stream 等消耗资源的 Provider 都应加 .autoDispose

family — 接收参数

让 Provider 根据外部参数生成不同的实例:

dart
final articleDetailProvider = FutureProvider.autoDispose.family<String, String>(
  (ref, articleId) async {
    await Future.delayed(const Duration(seconds: 1));
    return '这是 $articleId 的详情内容';
  },
);

// 使用时传入参数
final detail = ref.watch(articleDetailProvider('article_001'));

组合使用

顺序固定:先 autoDispose,再 family

dart
// ✅ 正确
FutureProvider.autoDispose.family<String, String>(...)

// ❌ 错误
FutureProvider.family.autoDispose<String, String>(...)

Provider 之间互相依赖

Provider 可以依赖其他 Provider,实现自动派生计算:

dart
final productsProvider = Provider<List<Product>>((ref) => [
  const Product(name: '苹果', price: 5),
  const Product(name: '香蕉', price: 3),
  const Product(name: '橘子', price: 4),
]);

final cartProvider = NotifierProvider<CartNotifier, List<Product>>(CartNotifier.new);

// 总价 Provider —— 依赖购物车,自动计算
final totalPriceProvider = Provider<int>((ref) {
  final cart = ref.watch(cartProvider);
  return cart.fold<int>(0, (sum, item) => sum + item.price);
});

购物车内容变化时,totalPriceProvider 自动重新计算:

dart
Text('总计: ¥${ref.watch(totalPriceProvider)}')

代码生成方式(进阶)

前面介绍的都是手动写法——手写 Notifier 类 + 手写 Provider 变量。Riverpod 还支持代码生成写法,用注解代替手写模板代码,由工具自动生成 Provider 定义。

初学者建议先掌握手动写法,理解核心概念后再尝试代码生成。正式项目推荐代码生成,这也是 Riverpod 官方推荐的最佳实践。

手动 vs 代码生成对比

dart
// ── 手动写法 ──
class CounterNotifier extends Notifier<int> {
  @override
  int build() => 0;
  void increment() => state++;
}

final counterProvider = NotifierProvider<CounterNotifier, int>(CounterNotifier.new);


// ── 代码生成写法 ──
part 'counter.g.dart';

@riverpod
class Counter extends _$Counter {
  @override
  int build() => 0;
  void increment() => state++;
}
// counterProvider 自动生成在 counter.g.dart 中
手动写法代码生成写法
手动声明 Provider 变量自动生成
泛型容易写错自动推断
容易忘记加 autoDispose默认 autoDispose

安装

bash
flutter pub add riverpod_annotation
flutter pub add dev:riverpod_generator
flutter pub add dev:build_runner

生成代码

bash
# 一次性生成
dart run build_runner build

# 监听文件变化,自动重新生成(开发时推荐)
dart run build_runner watch

理解 part 指令

代码生成写法中,你一定会看到这行:

dart
part 'counter.g.dart';

part 是 Dart 语言的关键字,意思是「这个文件和另一个文件属于同一个库,共享私有成员」。

打个比方:

  • 你写了一个章节的正文(counter.dart
  • 有些模板内容让助手帮你写(counter.g.dart
  • part 告诉 Dart:「我们俩合起来才是完整的一章」

.g.dart 文件由 build_runner 自动生成,里面包含了 Provider 变量声明、基类定义等模板代码。

关键规则

  • part 'xxx.g.dart'; 中的文件名必须与当前文件对应:counter.dartcounter.g.dart
  • 用了 @riverpod 注解,就必须加上对应的 part 声明
  • 永远不要手动编辑 .g.dart 文件——每次运行 build_runner 都会重新覆盖
  • 继承的基类是 _$Xxx(带下划线前缀),不是 Notifier——这个基类也是自动生成的

理解 @riverpod 注解

代码生成提供两种形式:

函数形式——只提供值,不需要修改状态:

dart
@riverpod
int count(Ref ref) => 0;
// 自动生成 countProvider

类形式——需要修改状态:

dart
@riverpod
class Counter extends _$Counter {
  @override
  int build() => 0;
  void increment() => state++;
}
// 自动生成 counterProvider
对比函数形式类形式
能修改状态
适用路由、配置、计算值计数器、购物车
生成 Provider 名函数名 + Provider类名(首字母小写)+ Provider

@riverpod vs @Riverpod(keepAlive: true)

写法生命周期
@riverpod没人监听时自动销毁(autoDispose)
@Riverpod(keepAlive: true)始终存活,直到应用结束

什么时候需要 keepAlive: true

场景需要 keepAlive?原因
路由配置 (GoRouter)应用级基础设施,不能被销毁
用户登录信息全局状态,不能丢
主题设置全局配置,始终需要
页面搜索结果离开页面后不需保留
商品详情看完即走
dart
@Riverpod(keepAlive: true)
GoRouter appRouter(Ref ref) {
  return GoRouter(
    initialLocation: AppRoutes.news,
    routes: [/* ... */],
  );
}

WARNING

拿不准就用默认的 @riverpod(自动销毁,更省内存)。滥用 keepAlive 会导致内存泄漏。

常用注解速查

注解作用示例
@riverpod默认 autoDispose 的 Provider@riverpod class Counter extends _$Counter { ... }
@Riverpod(keepAlive: true)不会被自动释放的 Provider全局配置、路由
@riverpod(函数)只读/计算型 Provider@riverpod int count(Ref ref) => 0;

完整示例:待办事项 App

综合前面所学,做一个待办事项应用:

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) {
    return MaterialApp(title: '待办事项', home: const TodoPage());
  }
}

// ============ 数据模型 ============

class Todo {
  final String title;
  final bool done;
  const Todo({required this.title, this.done = false});
  Todo copyWith({String? title, bool? done}) =>
      Todo(title: title ?? this.title, done: done ?? this.done);
}

// ============ 状态管理 ============

class TodoNotifier extends Notifier<List<Todo>> {
  @override
  List<Todo> build() => [];

  void add(String title) => state = [...state, Todo(title: title)];

  void toggle(int index) => state = [
    for (int i = 0; i < state.length; i++)
      if (i == index) state[i].copyWith(done: !state[i].done)
      else state[i],
  ];

  void remove(int index) => state = [...state]..removeAt(index);
}

final todoProvider = NotifierProvider<TodoNotifier, List<Todo>>(TodoNotifier.new);

// 派生状态:未完成数量
final uncompletedCountProvider = Provider<int>((ref) {
  final todos = ref.watch(todoProvider);
  return todos.where((t) => !t.done).length;
});

// ============ UI ============

class TodoPage extends ConsumerStatefulWidget {
  const TodoPage({super.key});
  @override
  ConsumerState<TodoPage> createState() => _TodoPageState();
}

class _TodoPageState extends ConsumerState<TodoPage> {
  final _controller = TextEditingController();

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  void _addTodo() {
    final text = _controller.text.trim();
    if (text.isEmpty) return;
    ref.read(todoProvider.notifier).add(text);
    _controller.clear();
  }

  @override
  Widget build(BuildContext context) {
    final todos = ref.watch(todoProvider);
    final remaining = ref.watch(uncompletedCountProvider);

    return Scaffold(
      appBar: AppBar(title: Text('待办事项 ($remaining 未完成)')),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(12),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _controller,
                    decoration: const InputDecoration(
                      hintText: '输入待办事项...',
                      border: OutlineInputBorder(),
                    ),
                    onSubmitted: (_) => _addTodo(),
                  ),
                ),
                const SizedBox(width: 8),
                ElevatedButton(onPressed: _addTodo, child: const Text('添加')),
              ],
            ),
          ),
          Expanded(
            child: todos.isEmpty
                ? const Center(child: Text('暂无待办事项'))
                : ListView.builder(
                    itemCount: todos.length,
                    itemBuilder: (context, index) {
                      final todo = todos[index];
                      return ListTile(
                        leading: Checkbox(
                          value: todo.done,
                          onChanged: (_) =>
                              ref.read(todoProvider.notifier).toggle(index),
                        ),
                        title: Text(todo.title,
                          style: TextStyle(
                            decoration: todo.done
                                ? TextDecoration.lineThrough
                                : TextDecoration.none,
                          ),
                        ),
                        trailing: IconButton(
                          icon: const Icon(Icons.delete),
                          onPressed: () =>
                              ref.read(todoProvider.notifier).remove(index),
                        ),
                      );
                    },
                  ),
          ),
        ],
      ),
    );
  }
}

Riverpod vs Provider 快速对比

对比项ProviderRiverpod
定义状态继承 ChangeNotifierProvider / NotifierProvider
注入方式Widget 树中用 Provider 包裹顶层 ProviderScope,Provider 定义在外部
读取方式context.watch() / context.read()ref.watch() / ref.read()
Widget 基类StatelessWidget / StatefulWidgetConsumerWidget / ConsumerStatefulWidget
依赖 BuildContext
编译时安全
同类型多个 Provider冲突通过名称区分

Legacy API — 旧版写法

WARNING

以下 API 在 flutter_riverpod 3.x 中已废弃,从主库移除。如需使用须额外引入 legacy.dart。新项目请直接使用新版 API。

dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod/legacy.dart';  // 旧版 API

StateProvider → 迁移到 NotifierProvider

dart
// ❌ 旧版
final counterProvider = StateProvider<int>((ref) => 0);
// 修改:ref.read(counterProvider.notifier).state++;

// ✅ 新版
class CounterNotifier extends Notifier<int> {
  @override
  int build() => 0;
  void increment() => state++;
}
final counterProvider = NotifierProvider<CounterNotifier, int>(CounterNotifier.new);

StateNotifierProvider → 迁移到 NotifierProvider

dart
// ❌ 旧版(需额外引入 state_notifier 包)
class CartNotifier extends StateNotifier<List<Product>> {
  CartNotifier() : super([]);
  void add(Product product) => state = [...state, product];
}
final cartProvider = StateNotifierProvider<CartNotifier, List<Product>>(
  (ref) => CartNotifier(),
);

// ✅ 新版(内置支持,无需额外包)
class CartNotifier extends Notifier<List<Product>> {
  @override
  List<Product> build() => [];
  void add(Product product) => state = [...state, product];
}
final cartProvider = NotifierProvider<CartNotifier, List<Product>>(CartNotifier.new);

新旧 API 对照

旧版新版说明
StateProvider<T>NotifierProvider<XxxNotifier, T>简单值
StateNotifierProvider<XxxNotifier, T>NotifierProvider<XxxNotifier, T>复杂逻辑
AsyncNotifierProvider<XxxNotifier, T>异步初始化
.notifier.state = value.notifier.method()新版通过方法修改,更安全

常见问题

1. Provider 没找到?

确保 ProviderScope 包裹了整个 App,且 Provider 定义在 Widget 之外(通常是全局变量)。

2. 状态更新了但 UI 没刷新?

检查是否用了 ref.read() 而不是 ref.watch() 来显示数据。显示数据必须用 watch

3. Notifier 中修改状态没生效?

状态更新必须是不可变的,要创建新对象:

dart
// ❌ 直接修改
state.add(product);  // UI 不会更新!

// ✅ 创建新列表
state = [...state, product];  // UI 会更新

4. StateProvider 报错 "The function 'StateProvider' isn't defined"?

StateProviderflutter_riverpod 3.x 中已移除,需额外引入:

dart
import 'package:flutter_riverpod/legacy.dart';

或迁移到 NotifierProvider,详见 Legacy API

5. NotifierProvider 和 AsyncNotifierProvider 怎么选?

  • 初始值可以直接确定 → NotifierProvider
  • 初始值需要异步获取 → AsyncNotifierProvider

下一步

基于 Flutter 官方文档整理