flutter状态管理入门教程-Riverpod

Riverpod 状态管理完整教程

目录

  1. [什么是 Riverpod](#什么是 Riverpod)
  2. [为什么选择 Riverpod](#为什么选择 Riverpod)
  3. 环境安装
  4. 核心概念
  5. [Provider 的类型](#Provider 的类型)
  6. 读取状态
  7. 修改状态
  8. 异步状态管理
  9. 依赖注入与覆盖
  10. 最佳实践
  11. 常见问题

什么是 Riverpod

Riverpod 是 Flutter 生态中一款编译时安全、可测试、不依赖 Widget 树的状态管理方案,由 Provider 的原作者 Remi Rousselet 设计。它解决了 Provider 在编译期无法检测、需要 BuildContext 等问题。

Riverpod 的核心设计特点:

  • 编译时安全:错误的 Provider 引用会在编译期报错,而不是运行期崩溃。
  • 不依赖 BuildContext:可以在任意位置(包括纯 Dart 代码)读取状态。
  • 可测试:Provider 可以被轻松覆盖(override)以进行单元测试。
  • 自动内存管理:Widget 不再使用时,相关的状态监听会自动释放。

为什么选择 Riverpod

特性 Provider Riverpod
编译时安全 否 是
需要 BuildContext 是 否
代码生成 可选 强烈推荐
自动内存管理 部分 是
与 Flutter 版本耦合 较紧 松

如果你正在使用 Provider 或 InheritedWidget,迁移到 Riverpod 可以显著减少因错用 context 导致的运行时错误。


环境安装

在 pubspec.yaml 中添加依赖:

yaml 复制代码
dependencies:
  flutter:
    sdk: flutter
  flutter_riverpod: ^2.5.1
  # 如果喜欢 Hooks 风格,再加:
  hooks_riverpod: ^2.5.1
  flutter_hooks: ^0.20.5

然后运行:

bash 复制代码
flutter pub get

启用代码生成(推荐)

Riverpod 2.x 推荐通过 build_runner 生成 Provider。开发依赖如下:

yaml 复制代码
dev_dependencies:
  build_runner: ^2.4.11
  riverpod_generator: ^2.4.0
  custom_lint: ^0.6.4
  riverpod_lint: ^2.3.10

生成命令:

bash 复制代码
dart run build_runner watch -d

核心概念

Riverpod 有三个核心要素:

  1. Provider:状态定义。
  2. Consumer:状态的读取者,通常是 Widget。
  3. WidgetRef / Ref:用于读取或修改状态的引用。

最小示例

dart 复制代码
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// 1. 定义一个 Provider
final counterProvider = StateProvider<int>((ref) => 0);

void main() {
  // 2. 用 ProviderScope 包裹应用
  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Riverpod Demo')),
        body: const CounterView(),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 3. 读取状态
    final count = ref.watch(counterProvider);

    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text('Count: $count'),
          ElevatedButton(
            onPressed: () {
              // 4. 修改状态
              ref.read(counterProvider.notifier).state++;
            },
            child: const Text('Increment'),
          ),
        ],
      ),
    );
  }
}

关键说明

  • ProviderScope 必须放在应用最外层,它是所有 Provider 的容器。
  • ConsumerWidget 替代 StatelessWidget,提供 WidgetRef。
  • ref.watch(provider) 用于监听状态变化并重建 Widget。
  • ref.read(provider) 用于读取一次状态,常用于事件回调中。

Provider 的类型

Riverpod 提供了多种 Provider,适用于不同场景。

1. Provider

用于提供不可变 或派生的值。

dart 复制代码
final greetingProvider = Provider<String>((ref) => 'Hello Riverpod');

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final greeting = ref.watch(greetingProvider);
    return Text(greeting);
  }
}

2. StateProvider

用于简单的可变状态,如计数器、开关状态等。

dart 复制代码
final darkModeProvider = StateProvider<bool>((ref) => false);

SwitchListTile(
  title: const Text('Dark Mode'),
  value: ref.watch(darkModeProvider),
  onChanged: (value) => ref.read(darkModeProvider.notifier).state = value,
)

3. FutureProvider

用于封装异步操作,如 HTTP 请求。

dart 复制代码
final userProvider = FutureProvider<User>((ref) async {
  final response = await http.get(Uri.parse('https://api.example.com/user'));
  return User.fromJson(jsonDecode(response.body));
});

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

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

    return asyncUser.when(
      data: (user) => Text(user.name),
      loading: () => const CircularProgressIndicator(),
      error: (err, stack) => Text('Error: $err'),
    );
  }
}

4. StreamProvider

用于监听实时数据流,如 WebSocket、Firebase 实时数据等。

dart 复制代码
final messagesProvider = StreamProvider<List<Message>>((ref) {
  return FirebaseFirestore.instance
      .collection('messages')
      .snapshots()
      .map((snapshot) => snapshot.docs.map((e) => Message.fromJson(e.data())).toList());
});

5. StateNotifierProvider / AsyncNotifier

用于复杂状态逻辑。Riverpod 2.x 推荐使用 AsyncNotifier 处理异步逻辑,StateNotifier 仍可用于同步场景。

dart 复制代码
@riverpod
class Counter extends _$Counter {
  @override
  int build() => 0;

  void increment() => state++;
  void decrement() => state--;
}

生成的 Provider 使用方式:

dart 复制代码
final count = ref.watch(counterProvider);
ref.read(counterProvider.notifier).increment();

读取状态

ref.watch

当 Provider 状态变化时,自动重建 Widget。

dart 复制代码
final count = ref.watch(counterProvider);

ref.read

只读取当前值,不建立监听关系。适合在按钮点击等事件中使用。

dart 复制代码
onPressed: () => ref.read(counterProvider.notifier).increment(),

ref.listen

监听状态变化并执行副作用,如弹出 Snackbar、跳转页面。

dart 复制代码
@override
Widget build(BuildContext context, WidgetRef ref) {
  ref.listen<AsyncValue<User>>(userProvider, (previous, next) {
    next.whenOrNull(
      error: (error, _) => ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(error.toString())),
      ),
    );
  });

  return Container();
}

选择性监听

使用 select 只监听对象的部分字段,避免不必要的重建。

dart 复制代码
final name = ref.watch(userProvider.select((user) => user.name));

修改状态

简单状态

dart 复制代码
ref.read(counterProvider.notifier).state++;

复杂状态(StateNotifier)

dart 复制代码
class TodoNotifier extends StateNotifier<List<Todo>> {
  TodoNotifier() : super([]);

  void add(String title) {
    state = [...state, Todo(title: title, completed: false)];
  }

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

final todoProvider = StateNotifierProvider<TodoNotifier, List<Todo>>((ref) {
  return TodoNotifier();
});

调用:

dart 复制代码
ref.read(todoProvider.notifier).add('Buy milk');

异步状态管理

使用 AsyncNotifier 处理需要等待网络或数据库的场景。

dart 复制代码
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'user_notifier.g.dart'; // 由 build_runner 生成

@riverpod
class UserNotifier extends _$UserNotifier {
  @override
  Future<User> build() async {
    return await _fetchUser();
  }

  Future<User> _fetchUser() async {
    final response = await http.get(Uri.parse('https://api.example.com/user'));
    return User.fromJson(jsonDecode(response.body));
  }

  Future<void> refresh() async {
    state = const AsyncLoading();
    state = await AsyncValue.guard(_fetchUser);
  }
}

在 UI 中使用:

dart 复制代码
final user = ref.watch(userNotifierProvider);

return user.when(
  data: (user) => Text(user.name),
  loading: () => const CircularProgressIndicator(),
  error: (err, stack) => Column(
    children: [
      Text('加载失败: $err'),
      ElevatedButton(
        onPressed: () => ref.read(userNotifierProvider.notifier).refresh(),
        child: const Text('重试'),
      ),
    ],
  ),
);

依赖注入与覆盖

Riverpod 允许在测试中覆盖 Provider,非常方便。

在测试中覆盖

dart 复制代码
void main() {
  testWidgets('Counter increments', (tester) async {
    await tester.pumpWidget(
      ProviderScope(
        overrides: [
          counterProvider.overrideWith((ref) => 42),
        ],
        child: const MyApp(),
      ),
    );

    expect(find.text('Count: 42'), findsOneWidget);
  });
}

在运行时覆盖

dart 复制代码
ProviderScope(
  overrides: [
    apiBaseUrlProvider.overrideWithValue('https://staging.example.com'),
  ],
  child: const MyApp(),
)

最佳实践

  1. 优先使用代码生成 :用 @riverpod 注解生成 Provider,减少手写错误。
  2. Widget 尽量薄:将业务逻辑放在 Notifier 中,Widget 只负责展示。
  3. 避免在 build 中使用 ref.read:除非确实只需要一次性读取。
  4. 使用 select 精确监听:避免大对象变化导致全量重建。
  5. 将 Provider 放在独立文件:每个业务模块一个文件,便于维护。
  6. 异步操作使用 AsyncNotifier:统一处理 loading、error、data 状态。

常见问题

Q1: ref.read 和 ref.watch 有什么区别?

  • ref.watch:建立监听,状态变化时重建。 ref.read:只读取当前值,不重建。

Q2: 为什么状态没有更新?

  • 检查是否调用了 .notifier 或 .state。
  • 确保对象是不可变的,直接修改对象字段不会触发重建。

Q3: 如何处理 Provider 之间的依赖?

dart 复制代码
final userIdProvider = Provider<String>((ref) => '123');

final userProfileProvider = FutureProvider<User>((ref) async {
  final userId = ref.watch(userIdProvider);
  return await fetchUser(userId);
});

Q4: 与 Bloc、GetX 相比如何?

维度 Riverpod Bloc GetX
学习曲线 中 高 低
编译安全 高 中 低
可测试性 高 高 中
社区生态 增长快 成熟 成熟

结语

Riverpod 通过编译时安全和清晰的架构,让 Flutter 状态管理更加可靠。建议新项目直接使用 Riverpod 2.x 配合代码生成,老项目可以逐步迁移。掌握 Provider、StateProvider、FutureProvider、AsyncNotifier 四种核心类型,足以应对绝大多数 Flutter 应用场景。

相关推荐
愚坤8 小时前
前端真有意思,JS还能独立做项目?
前端·javascript·github
饮水桶_T8 小时前
JAVA IO流笔记
开发语言·数据结构·算法
李纲明9 小时前
微信小程序 getSystemInfo 提示已废弃、屏幕高度算错胶囊还对不齐?getWindowInfo 和 getDeviceInfo 怎么接才稳
javascript·微信小程序·小程序·小程序开发·devdiy
Felven10 小时前
C. Brr Brrr Patapim
c语言·开发语言
FungLeo10 小时前
成为全栈·Vue 管理后台篇·用 fetch 建一层可控的请求内核
前端·javascript·vue.js·http·fetch·formdata·接口封装
用户842981424181011 小时前
ByteJS,让重要的 JavaScript 以字节码运行。
javascript
路溪非溪12 小时前
python标准库使用总结
开发语言·python
FungLeo12 小时前
成为全栈·Vue 管理后台篇·Vue Router如何守住多角色后台边界
前端·javascript·vue.js·rbac·vue router·路由守卫·前端权限
FungLeo13 小时前
成为全栈·Vue管理后台篇·仪表盘不是几张卡片:组合统计接口与角色视图
前端·javascript·vue.js·rbac·仪表盘·管理后台·tanstack query
程序员大阳13 小时前
HTML+CSS+JS对话式实战(7)-学生端活动报名功能开发
javascript·css·html·活动报名