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 应用场景。

相关推荐
system_error_x1 小时前
C++ 编程基础入门教程
开发语言·c++
Amos_Web2 小时前
Markdonw语法
开发语言·前端·javascript
全栈项目管理程序猿2 小时前
ArcGIS JS 基础教程(34):体元系列 - 等值面 VoxelIsosurface
前端·javascript
SensorFlow2 小时前
离页埋点:sendBeacon 返回 true,为什么还不能算接收成功
javascript
Wang's Blog2 小时前
Java 项目实战: 交友项目 - 回顾和今日内容介绍
java·开发语言·交友
兔子零10242 小时前
架构师最危险的能力:用正确的技术,做出错误的架构决策
前端·javascript·后端
Bug收容所2 小时前
Vue+js Day2
前端·javascript·vue.js
初心未改HD2 小时前
DSH Skill开发实战:SKILL.md是给LLM看的操作手册,不是给人看的README,踩了4个坑
java·大数据·开发语言
Wang's Blog3 小时前
Java 项目实战: 交友项目 - SpringData-Mongo 环境搭建和 CRUD
java·开发语言·交友