Flutter Riverpod 使用详细解析

发布于:2025-06-27 ⋅ 阅读:(17) ⋅ 点赞:(0)
📚 Flutter 状态管理系列文章目录
  1. Flutter 状态管理(setState、InheritedWidget、 Provider 、Riverpod、 BLoC / Cubit、 GetX 、MobX 、Redux)

  2. setState() 使用详解:原理及注意事项

  3. InheritedWidget 组件使用及原理

  4. Flutter 中 Provider 的使用、注意事项与原理解析(含代码实战)

  5. GetX 用法详细解析以及注意事项

  6. Flutter BLoC 使用详细解析

  7. Flutter MobX 响应式原理与实战详解

  8. Flutter Riverpod 使用详细解析

  9. Riverpod原理解析(实现一个自己的Riverpod

源码地址

📌 Riverpod 是 Flutter 官方推荐的状态管理方案之一,相比 Provider 更灵活、安全、支持组合、异步、热重载、IDE 类型推导。


📁 1. 安装依赖

pubspec.yaml 添加:

dependencies:
  flutter_riverpod: ^2.5.1

然后执行:

flutter pub get

🧠 2. 核心概念简述

类型 描述 示例用途
Provider 只读数据 常量、服务类
StateProvider 原始值的可变状态(int/bool) 计数器、开关
StateNotifierProvider 复杂对象状态 + 控制逻辑 列表、对象、业务逻辑
ChangeNotifierProvider ChangeNotifier 封装的旧模式 第三方 SDK 封装
FutureProvider 异步加载 网络请求、初始化数据
StreamProvider 实时数据流 WebSocket、定时器

🧪 3. 实战代码讲解

示例场景:Todo 应用


Provider:静态或只读依赖

final greetingProvider = Provider<String>((ref) {
  return "Hello, Riverpod!";
});

class GreetingWidget extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    final greeting = ref.watch(greetingProvider);
    return Text(greeting);
  }
}

💡 用于配置、单例、不可变依赖。


StateProvider:简单状态(如 int、bool)

final counterProvider = StateProvider<int>((ref) => 0);

class CounterPage extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);
    return Column(
      children: [
        Text('Count: $count'),
        ElevatedButton(
          onPressed: () => ref.read(counterProvider.notifier).state++,
          child: Text('Increment'),
        ),
      ],
    );
  }
}

💡 简单值状态管理的首选。


StateNotifierProvider:结构化业务逻辑

定义模型:

class Todo {
  final String id;
  final String title;
  Todo({required this.id, required this.title});
}

定义状态控制器:

class TodoNotifier extends StateNotifier<List<Todo>> {
  TodoNotifier() : super([]);

  void add(String title) {
    final todo = Todo(id: DateTime.now().toIso8601String(), title: title);
    state = [...state, todo];
  }

  void remove(String id) {
    state = state.where((t) => t.id != id).toList();
  }
}

final todoListProvider =
    StateNotifierProvider<TodoNotifier, List<Todo>>((ref) => TodoNotifier());

页面使用:

class TodoListView extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    final todos = ref.watch(todoListProvider);
    return ListView(
      children: todos
          .map((t) => ListTile(
                title: Text(t.title),
                trailing: IconButton(
                  icon: Icon(Icons.delete),
                  onPressed: () => ref.read(todoListProvider.notifier).remove(t.id),
                ),
              ))
          .toList(),
    );
  }
}

💡 面向对象式的推荐写法,利于维护与测试。


ChangeNotifierProvider:传统类兼容封装

class AuthModel extends ChangeNotifier {
  String _email = '';
  String get email => _email;

  void login(String email) {
    _email = email;
    notifyListeners();
  }
}

final authProvider = ChangeNotifierProvider((ref) => AuthModel());

使用:

class AuthView extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    final auth = ref.watch(authProvider);
    return Column(
      children: [
        Text('Email: ${auth.email}'),
        ElevatedButton(
          onPressed: () => ref.read(authProvider).login('user@example.com'),
          child: Text('Login'),
        ),
      ],
    );
  }
}

⚠️ 尽量用于兼容旧代码或外部库。


FutureProvider:异步加载数据

final userInfoProvider = FutureProvider<String>((ref) async {
  await Future.delayed(Duration(seconds: 1));
  return "Async User Loaded";
});

使用:

class UserInfoWidget extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    final userInfo = ref.watch(userInfoProvider);
    return userInfo.when(
      data: (name) => Text('User: $name'),
      loading: () => CircularProgressIndicator(),
      error: (e, _) => Text('Error: $e'),
    );
  }
}

💡 支持 loading/error/data 三种状态,十分方便。


StreamProvider:实时流监听

final tickProvider = StreamProvider<int>((ref) async* {
  int count = 0;
  while (true) {
    await Future.delayed(Duration(seconds: 1));
    yield ++count;
  }
});

使用:

class TimerWidget extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    final tick = ref.watch(tickProvider);
    return tick.when(
      data: (val) => Text('Tick: $val'),
      loading: () => Text('Waiting...'),
      error: (e, _) => Text('Error: $e'),
    );
  }
}

💡 非常适合实现 WebSocket、倒计时、后台上传监听等。



🧬 4 Riverpod Provider 的生命周期详解

Riverpod 的每个 Provider 都有清晰的生命周期管理机制,这是它相比 Provider(老版)或 setState 更加安全、强大的关键优势之一。

生命周期由 Riverpod 自动管理,确保资源释放、缓存优化、避免内存泄漏。


🎯 生命周期触发时机概览
生命周期阶段 描述 对应钩子函数 / 方法
创建 第一次被 ref.watch() / ref.read() 使用 provider 的构造函数
监听中 有 widget 或 provider 依赖它 ref.watch() 触发监听
取消监听 最后一个监听者移除后,开始进入 dispose 倒计时 keepAlive 决定回收策略
销毁 无依赖 + 被标记为回收 onDispose 触发

📌 1. Provider 的创建与首次使用
  • 只有在首次使用(如 ref.watch())时才真正创建。
  • Provider 是懒加载的(Lazy Initialized)。
final timestampProvider = Provider<DateTime>((ref) {
  print('✅ 创建时间: ${DateTime.now()}');
  return DateTime.now();
});

📌 2. 自动销毁与缓存策略
🔁 默认行为:自动销毁(AutoDispose)
  • 若无任何 ref.watch() 使用该 Provider,它会被自动销毁。
  • 优化内存和性能,避免长时间驻留。
final myProvider = Provider.autoDispose<int>((ref) {
  print('🌀 创建');
  ref.onDispose(() => print('❌ 销毁'));
  return 42;
});

使用中断后会触发销毁:

// 页面离开,或 ref.invalidate()

📌 3. 保持活跃 ref.keepAlive()
final counterProvider = Provider.autoDispose<int>((ref) {
  final link = ref.keepAlive(); // 👈 阻止销毁

  Future.delayed(Duration(seconds: 30), () {
    print('🚨 30 秒后允许销毁');
    link.close(); // 👈 恢复可销毁
  });

  ref.onDispose(() {
    print('⛔ 被销毁');
  });

  return 123;
});

✅ 用于防止高频初始化,如网络缓存、状态记录。


📌 4. ref.onDispose()

所有 Provider 都可使用:

ref.onDispose(() {
  print('🧹 清理资源...');
});

常用于:

  • 关闭连接(如 WebSocket、Stream、Timer)
  • 释放资源(如 Controller、Database)
  • 终止订阅

✅ 总结:各 Provider 生命周期对比
Provider 类型 是否可自动销毁 是否可 keepAlive 是否有 onDispose
Provider ❌ 否(持久) ✅ 支持 ✅ 支持
Provider.autoDispose ✅ 是 ✅ 支持 ✅ 支持
StateProvider ❌ 否 ✅ 支持 ✅ 支持
StateNotifierProvider ❌ 否(除非手动) ✅ 支持 ✅ 支持
FutureProvider.autoDispose ✅ 默认 ✅ 支持 ✅ 支持
StreamProvider.autoDispose ✅ 默认 ✅ 支持 ✅ 支持

🛠 应用实战建议
场景 建议 Provider 类型
临时页面数据(如表单页) .autoDispose 类型
全局共享状态(如登录信息) 普通 Provider / StateNotifierProvider
长连接、Timer、Stream ref.onDispose + .keepAlive()
一次性请求缓存(配置、Token) 使用 keepAlive() 手动控制回收

✅ 5. 多 Provider 合并管理

final appProviders = ProviderContainer(overrides: []);

或使用 ConsumerStatefulWidget 在生命周期中集中监听多个状态。


🧱 6. Riverpod 与传统Provider 的关系

✅ 简明回答:

Riverpod 并不是基于传统 Provider 的封装,它是 完全重写的一个全新状态管理框架,由同一个作者 Remi Rousselet 开发,但底层逻辑、API 设计、运行机制都不同。


🔍 深度解析:Riverpod 与 Provider 的关系
项目 Provider Riverpod
作者 Remi Rousselet Remi Rousselet
上线时间 2018 2020
设计理念 面向 widget tree 的 InheritedWidget 独立于 widget tree,可纯 Dart 运行
架构关系 Flutter Widget 的封装 重新设计的响应式依赖系统(非封装)
生命周期 依赖 Widget 生命周期 由容器独立管理,更安全可控
热重载支持 完整支持热重载(尤其适合大型项目)
Test 测试 需依赖 Widget 或 MockContext 完全脱离 UI,可单测 provider

🧠 核心区别举例
✅ Provider 示例
ChangeNotifierProvider(
  create: (_) => CounterModel(),
  child: MyApp(),
);
  • 必须包在 widget tree 里,依赖 BuildContext
  • 很多依赖关系写在 UI 组件中,不利于拆分测试。

✅ Riverpod 示例
final counterProvider = StateProvider((ref) => 0);
  • 不依赖 WidgetTree,任何地方都可以 ref.watch
  • 甚至可以在纯 Dart 类、后台逻辑中使用,适合模块化架构。

📦 为什么作者重写 Riverpod?

作者自己在 Riverpod 官网解释了:

“Provider 的设计绑定在 widget tree 上,不足以应对复杂、灵活的状态管理需求;Riverpod 是为了解决 Provider 的根本限制而设计的。”

来源:https://riverpod.dev


✅ 总结对比(类比说明)
类比对象 Provider Riverpod
类比 Flutter 状态 setState() Bloc / Redux / MobX
类比 HTML 框架 jQuery React + Hooks
是否独立可测 ❌ 需要 widget tree ✅ 完全支持纯 Dart 单测
是否推荐未来使用 🟡 小项目可继续用 ✅ 官方推荐未来项目默认使用 Riverpod

7. 🔄 Provider 到 Riverpod 迁移指南

✅ 1. 概念映射表
Provider 中的概念 Riverpod 对应概念 用途
ChangeNotifierProvider ChangeNotifierProvider(兼容) 保持一致,用于 SDK 封装
Provider<T> Provider<T> 只读依赖
StateProvider<T>(无) ✅ Riverpod 独有,推荐替代 setState 管理基本类型如 int、bool
ConsumerWidget ConsumerWidget 响应式 UI
context.read() ref.read() 读取值,不触发 rebuild
context.watch() ref.watch() 读取并监听
MultiProvider ❌ 不需要,Riverpod 使用全局注册 Provider 提升模块化与性能
依赖于 BuildContext ✅ 不依赖 BuildContext 更适合逻辑复用、单元测试

⚠️ 2. Provider 示例 VS Riverpod 对比代码
旧:使用 Provider(ChangeNotifier)
class CounterModel extends ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

// 注册
ChangeNotifierProvider(create: (_) => CounterModel())

// 使用
final counter = Provider.of<CounterModel>(context);

新:使用 Riverpod(StateNotifier)
final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {
  return CounterNotifier();
});

class CounterNotifier extends StateNotifier<int> {
  CounterNotifier() : super(0);
  void increment() => state++;
}

// UI 使用
class CounterWidget extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);
    return Text('$count');
  }
}

🔁 3. 推荐迁移步骤
步骤 内容
✅ 第一步 安装 flutter_riverpod 并用 ProviderScope 包裹 MaterialApp
✅ 第二步 从功能简单的模块开始迁移(如计数器页面)
✅ 第三步 ChangeNotifier 转为 StateNotifier / Notifier
✅ 第四步 context.read/watch 替换为 ref.read/watch
✅ 第五步 清除旧 Provider 引入,完全替换后删除 Provider

🤝 4. Riverpod 和 Provider 可以共存吗?

可以! 它们不会互相干扰,可用于渐进式迁移。

void main() {
  runApp(
    MultiProvider(
      providers: [ChangeNotifierProvider(create: (_) => OldProvider())],
      child: ProviderScope(
        child: MyApp(),
      ),
    ),
  );
}

📌 注意顺序,ProviderScope 是 Riverpod 的入口容器,MultiProvider 是旧 Provider 的入口。


🧪 5. 测试更简单

Riverpod 的 Provider 不依赖 Widget Tree,可直接测试:

void main() {
  test('Counter increments', () {
    final container = ProviderContainer();
    final notifier = container.read(counterProvider.notifier);
    notifier.increment();
    expect(container.read(counterProvider), 1);
  });
}

🧭 6. 补充推荐迁移写法
✅ 将 Provider 包装类抽离为方法:
Widget build(BuildContext context, WidgetRef ref) {
  final count = ref.watch(counterProvider);
  return _buildCountText(count);
}

Widget _buildCountText(int count) => Text('$count');

✅ 避免业务逻辑写在 build 方法中,便于测试和维护。


📚 工具推荐
工具 用途
riverpod_lint Riverpod 专用 Lint
riverpod_generator 自动生成 Notifier 等模板
state_notifier 搭配使用,可手动控制状态
freezed 状态模型不可变 + 模式匹配

✅ 小结:迁移推荐策略
  • 🧱 小项目:直接重构成 Riverpod(更清晰)
  • 🧬 中大型项目:新模块优先使用 Riverpod,旧模块稳定后再迁移
  • 🧪 单元测试驱动:逻辑类迁移优先(先把模型和 service 拆出来)
  • 🔁 可并存:逐步过渡,保持主 app 稳定

下面是一个完整的 计数器 Counter 示例,包括:

  • ✅ Provider 旧写法(使用 ChangeNotifier
  • ✅ Riverpod 新写法(使用 StateNotifier

每一套代码都包含完整的 main.dartmodelUI,你可以直接运行进行对比。


✅ 迁移前(使用 Provider)
📄 pubspec.yaml 添加依赖
dependencies:
  flutter:
    sdk: flutter
  provider: ^6.1.0

📁 counter_model.dart
import 'package:flutter/foundation.dart';

class CounterModel extends ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

📄 main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'counter_model.dart';

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => CounterModel(),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: CounterPage(),
    );
  }
}

class CounterPage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    final counter = Provider.of<CounterModel>(context);

    return Scaffold(
      appBar: AppBar(title: Text('Provider Counter')),
      body: Center(child: Text('Count: ${counter.count}', style: TextStyle(fontSize: 24))),
      floatingActionButton: FloatingActionButton(
        onPressed: counter.increment,
        child: Icon(Icons.add),
      ),
    );
  }
}

✅ 迁移后(使用 Riverpod)
📄 pubspec.yaml 添加依赖
dependencies:
  flutter:
    sdk: flutter
  flutter_riverpod: ^2.5.1

📁 counter_notifier.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';

// Provider 和逻辑
class CounterNotifier extends StateNotifier<int> {
  CounterNotifier() : super(0);

  void increment() => state++;
  void reset() => state = 0;
}

final counterProvider = StateNotifierProvider<CounterNotifier, int>(
  (ref) => CounterNotifier(),
);

// 🚀 抽离逻辑方法(对外暴露 API)
int useCounter(WidgetRef ref) => ref.watch(counterProvider);

void incrementCounter(WidgetRef ref) => ref.read(counterProvider.notifier).increment();

void resetCounter(WidgetRef ref) => ref.read(counterProvider.notifier).reset();


📄 main.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'counter_controller.dart';

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

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

  
  Widget build(BuildContext context) {
    return MaterialApp(home: CounterPage());
  }
}

class CounterPage extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    final count = useCounter(ref); // 👈 抽离的读取方法

    return Scaffold(
      appBar: AppBar(title: Text('抽离逻辑示例')),
      body: Center(child: Text('Count: $count', style: TextStyle(fontSize: 24))),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton.small(
            heroTag: 'add',
            onPressed: () => incrementCounter(ref), // 👈 抽离的操作方法
            child: Icon(Icons.add),
          ),
          const SizedBox(width: 10),
          FloatingActionButton.small(
            heroTag: 'reset',
            onPressed: () => resetCounter(ref), // 👈 抽离 reset 方法
            child: Icon(Icons.refresh),
          ),
        ],
      ),
    );
  }
}


🧭 对比总结
项目 Provider Riverpod
状态类 ChangeNotifier StateNotifier<int>
状态读取 Provider.of() ref.watch()
状态变更 notifyListeners() state = ...
UI绑定 StatelessWidget + Provider ConsumerWidget
生命周期控制 依赖 Widget Tree 独立容器控制(ProviderScope)

🧭 8. 最佳实践建议

建议 理由或说明
使用 StateNotifier 替代 ChangeNotifier 更轻量、更明确、更好测试
避免直接 ref.read().state++ 推荐封装方法防止状态逻辑混乱
拆分 Provider 文件 提高可读性和可维护性
ref.read() 写到方法中 避免在 UI 中写业务逻辑

📦 附加资源推荐

  • 官方文档:https://riverpod.dev
  • IDE 插件:Riverpod Snippets
  • 推荐组合:Riverpod + go_router + freezed + dio

源码地址

📚 Flutter 状态管理系列文章目录
  1. Flutter 状态管理(setState、InheritedWidget、 Provider 、Riverpod、 BLoC / Cubit、 GetX 、MobX 、Redux)

  2. setState() 使用详解:原理及注意事项

  3. InheritedWidget 组件使用及原理

  4. Flutter 中 Provider 的使用、注意事项与原理解析(含代码实战)

  5. GetX 用法详细解析以及注意事项

  6. Flutter BLoC 使用详细解析

  7. Flutter MobX 响应式原理与实战详解

  8. Flutter Riverpod 使用详细解析

  9. Riverpod原理解析(实现一个自己的Riverpod


网站公告

今日签到

点亮在社区的每一天
去签到