本篇文章主要介绍以下几个知识点:
- 分层架构设计
- Provider vs Riverpod
- Riverpod 在实际项目中的使用示例

1. 分层架构设计
Android 应用架构指南:https://developer.android.com/topic/architecture?hl=zh-cn
使用 Riverpod 的 Flutter 应用架构指南:https://codewithandrea.com/articles/flutter-app-architecture-riverpod-introduction/
1.1 Flutter 分层架构概述

基于 Clean Architecture 原则,Flutter 应用采用分层架构确保关注点分离:
┌─────────────────────────────────────┐
│ Presentation Layer │ ← UI 层
├─────────────────────────────────────┤
│ Application Layer │ ← 业务逻辑层
├─────────────────────────────────────┤
│ Domain Layer │ ← 领域层
├─────────────────────────────────────┤
│ Data Layer │ ← 数据层
└─────────────────────────────────────┘
主要层次包括:
-
data layer:(数据层)
- 职责:数据获取、存储、网络请求
- 组件:Repositories, Data Sources, APIs
- 特点:处理数据的 CRUD 操作、抽象数据来源(网络、本地数据库)、数据转换和缓存策略
-
domain layer:(领域层)
- 职责:业务实体、业务规则(构造model模型)
- 组件:Models, Entities, Value Objects
- 特点:纯 Dart 代码(不依赖 Flutter)、定义数据结构和业务规则
-
application layer:(应用层)
- 职责:状态管理、业务流程编排
- 组件:StateNotifier, Provider, Use Cases
- 特点:协调不同领域服务、管理应用状态、处理用户操作的业务逻辑
-
presentation layer:(表现层)
- 职责:UI 组件、页面、用户交互
- 组件:Widgets, Pages, Dialogs
- 特点:只负责 UI 渲染和用户交互、通过 Riverpod Provider 获取状态、不包含业务逻辑
1.2 与 Android 架构对比

| 层级 | Flutter + Riverpod | Android MVVM |
|---|---|---|
| 表现层 | Widget + Consumer | View + Activity/Fragment |
| 应用层 | StateNotifier + Provider | ViewModel + LiveData/Flow |
| 领域层 | Domain Models | Use Cases(网域层,可选) |
| 数据层 | Repository + DataSource | Repository + Room/Retrofit |
相似点:
- 都遵循单向数据流
- 都有明确的层级分离
- 都使用观察者模式进行状态管理
差异点:
- Flutter 使用 Riverpod 的依赖注入更加简洁、响应式编程更加直观
- Android 领(网)域层是可选的,主要用于封装复杂业务逻辑或多个 ViewModel 共享的业务逻辑,对应 Flutter 中的应用层(项目中采用了 Riverpod 自动生成代码的方式时,也可以省略)。
- Flutter 的领域层更多用于定义数据模型和业务实体(只针对本文的架构)
2. Provider vs Riverpod
Provider 和 Riverpod 这两个库的作者都是 Remi Rousselet,新库命名是旧库的字母重排。
Provider 的优点是 简单易用,上手难度低,适用于应用规模较小,状态管理不太复杂的场景。
Provider 的局限如下:
依赖
BuildContext。
Provider 是基于InheritedWidget封装,读取状态需要BuildContext,所以 只能在Widget树中声明使用。
而在有些场景下不一定能直接拿到BuildContext,如在 非UI层 (如业务逻辑层) 访问状态,只能通过某种方式传递BuildContext实例,繁琐之余还增加了代码的耦合度。
使用不当,还可能导致ProviderNotFoundException。多个相同类型的 Provider,需要自己维护一个
Key进行区分。
如:Widget 树的同一层级,为相同类型的状态创建多个同类型的 Provider,子 Widget 无法确定使用哪个 Provider 的数据,需要指定一个特定的Key来进行区分:
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Counter(1), key: ValueKey(1)),
ChangeNotifierProvider(create: (_) => Counter(2), key: ValueKey(2)),
],
child: MyApp(),
),
);
}
// 通过key指定使用Counter实例
Provider.of<Counter>(context, listen: false, key: ValueKey(1)).increment();
- 如果需要跨 Widget 共享状态,Provider 就没法弄成局部私有的,只能是全局可访问的。
Riverpod 在 Provider 的基础上进行重构,解决上述问题之余,提供了 更灵活/精细的状态管理机制,状态不可变,编译时类型安全、易于测试等特性,更清晰的代码组织和维护方式 (注解代码生成),可以有效的组织和管理大规模的状态。
选择 Riverpod 的理由:
- 类型安全: 编译时检查,减少运行时错误
- 更好的性能: 精确的重建控制,避免不必要的 Widget 重建
- 简化的 API: 更直观的语法,减少样板代码
- 强大的开发工具: 更好的调试和开发体验
- 测试友好: 更容易进行单元测试和集成测试
3. Riverpod 基本使用与核心原理
3.1 Riverpod 基本使用
Riverpod 使用详解可参考:https://juejin.cn/post/7359402114018689076
官方文档:https://riverpod.dev/docs/introduction/why_riverpod
Riverpod 提供了多种状态管理模式,适用于不同的场景:
| 模式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| StateProvider | 简单状态 | 语法简单,直接修改 | 不适合复杂逻辑 |
| StateNotifier | 复杂状态 | 强类型,业务逻辑封装好 | 需要更多代码 |
| FutureProvider | 一次性异步 | 自动处理加载状态 | 不适合可变异步操作 |
| StreamProvider | 持续数据流 | 自动处理流状态 | 需要管理流的生命周期 |
| ChangeNotifier | 兼容旧代码 | 兼容性好 | 性能相对较差 |
| Notifier | 现代化状态管理 | 语法简洁,性能好 | 需要代码生成 |
3.2 Riverpod 核心原理
把应用的“数据源”和“依赖关系”想象成一张河网:上游的水质变化,会沿着支流层层传导到下游。
Riverpod 做的事情,就是把这张“依赖河网”用代码表达出来,并且自动完成“缓存、传导、重算、清理”。
这让复杂异步场景变得简单,正如官方所说:它是一个“响应式缓存框架”,专注于缓存与自动刷新。
-
核心对象
- ProviderContainer:一座“水库”,保存所有 provider 的缓存与依赖图,支持覆盖与作用域。
- Provider/Family:一段“取水逻辑”的声明,描述 value 如何被计算,是否带参数(family)。
- Ref(WidgetRef/Ref):提供读依赖、注册监听、生命周期回调(onDispose/keepAlive)的“水工”。
- Listener:下游订阅者,只有当感兴趣的上游发生变化时才会被通知(select 精准过滤)。
一个极简(伪)实现:
// 极简容器:保存缓存与依赖
class MiniContainer {
final Map<Object, dynamic> _cache = {};
final Map<Object, Set<Object>> _deps = {}; // provider -> its dependencies
T read<T>(MiniProvider<T> provider) {
if (_cache.containsKey(provider)) return _cache[provider] as T;
final tracker = _DependencyTracker(this, provider);
final value = provider.create(tracker);
_cache[provider] = value;
_deps[provider] = tracker.dependencies;
return value;
}
// 当依赖变更时,向下游传播“需要重算”的信号
void markDirty(Object changed) {
for (final entry in _deps.entries) {
if (entry.value.contains(changed)) {
_cache.remove(entry.key); // 使下游失效,下一次 read 时重算
markDirty(entry.key);
}
}
}
}
class _DependencyTracker {
final MiniContainer container;
final Object owner;
final Set<Object> dependencies = {};
_DependencyTracker(this.container, this.owner);
T watch<T>(MiniProvider<T> dep) {
dependencies.add(dep);
return container.read(dep);
}
}
class MiniProvider<T> {
final T Function(_DependencyTracker ref) create;
const MiniProvider(this.create);
}
上面伪代码展示了 Riverpod 的核心:
- read 第一次会计算并缓存 value;
- watch 让容器记录“谁依赖了谁”;
- 当上游变化,容器递归失效下游缓存;
- 下游在下次被读取/监听时自动重算。
4. 工作流实现
下面展示在实际项目中的工作流:
- 模块目录结构
lib/
├── api/
│ └── api_service.dart # api 相关
|
├── features/
│ ├── air/
│ ├── application/ # 应用层
│ │ └── providers.dart
│ ├── data/ # 数据层
│ │ └── history_repository.dart # 数据仓库
│ ├── domain/ # 领域层
│ │ ├── history.dart # 数据模型
│ │ └── history.g.dart
│ └── presentation/ # 表现层
│ ├── widget/ # 通用组件
│ │ └── chart_bar.dart
| ├── dialog/ # 对话框组件
│ └── statistics_page.dart # 统计UI页面
- 定义 API 接口
/// lib/api/api.dart
class Api {
static const String _path = '/dev/v1/';
// 获取指定时间段的历史数据:/user/nodes/tsdata
static const String historyData = "${_path}user/nodes/tsdata";
}
/// lib/api/api_service.dart
class ApiService {
// 获取24小时平均值
Future<Response> get24hoursData(
{required String nodeId,
required String paramName,
String type = "float"}) async {
var response = await _httpUtil
.request(Method.get, Api.historyData, queryParameters: {
"node_id": nodeId,
"param_name": "$paramIdentifier.$paramName",
"type": type,
"aggregate": "avg",
"aggregation_interval": "hour",
"num_intervals": "24"
});
return response;
}
}
- 定义数据仓库(data layer)
/// lib/features/air/data/history_repository.dart
class HistoryRepository {
final ApiService apiService;
HistoryRepository({required this.apiService});
// 获取24小时平均值
Future<History> get24hoursData(
{required String nodeId,
required String paramName,
String type = "float"}) async {
var response = await apiService.get24hoursData(
nodeId: nodeId, paramName: paramName, type: type);
Log.d("get24hoursData: ${response.data}");
return History.fromJson(response.data);
}
}
/// Providers
final historyRepositoryProvider = Provider<HistoryRepository>((ref) {
return HistoryRepository(apiService: ApiService.instance());
});
- 定义数据模型(domain layer)
import 'package:json_annotation/json_annotation.dart';
part 'history.g.dart';
/// lib/features/air/domain/history.dart
@JsonSerializable()
class History {
@JsonKey(name: "ts_data")
final List<TsDatum> tsData;
History({
required this.tsData,
});
factory History.fromJson(Map<String, dynamic> json) => _$HistoryFromJson(json);
Map<String, dynamic> toJson() => _$HistoryToJson(this);
}
- 实现状态管理(application layer)
/// 状态定义
class AirState {
final AsyncValue<History> data;
const AirState({
this.data = const AsyncLoading(),
});
AirState copyWith({AsyncValue<History>? data}) {
return AirState(data: data ?? this.data);
}
}
/// 状态管理器
class AirNotifier extends StateNotifier<AirState> {
final HistoryRepository _repository;
final String _nodeId;
AirNotifier(this._repository, this._nodeId) : super(const AirState());
// 获取历史数据
Future<History> getHistoryData() async {
final result = _repository.get24hoursData(_nodeId);
if (mounted) {
state = state.copyWith(data: result);
}
return result;
}
}
// 历史数据状态管理器 provider
final airNotifierProvider = StateNotifierProvider.autoDispose
.family<AirNotifier, AirState, String>((ref, nodeId) {
final repository = ref.watch(historyRepositoryProvider);
return AirNotifier(repository, nodeId);
});
- UI 层实现(presentation layer)
/// lib/features/air/presentation/statistics_page.dart
class StatisticsPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(airNotifierProvider('node_id'));
return state.when(
data: (data) => HistoryView(state),
loading: () => CircularProgressIndicator(),
error: (err, stack) => Text('Error: $err'),
);
}
}
小结:
这个工作流展示了完整的数据流向:
- API 定义 → 定义网络请求接口
- Repository 实现 → 处理数据获取、缓存、错误处理
- Domain 模型 → 定义业务数据结构
- StateNotifier → 管理复杂业务状态
- Provider 定义 → 提供依赖注入和状态访问
- UI 消费 → 响应式 UI 更新
数据流向:
UI (Consumer)
↓ ref.watch()
Provider
↓ StateNotifier
Application Layer
↓ Repository
Data Layer
↓ API/Database
External Data Source
5. 注意事项
- Provider 生命周期管理
// ❌ 错误:不必要的长期持有
final expensiveProvider = Provider<ExpensiveService>((ref) {
return ExpensiveService(); // 会一直存在内存中
});
// ✅ 正确:使用 autoDispose
final expensiveProvider = Provider.autoDispose<ExpensiveService>((ref) {
final service = ExpensiveService();
// 清理资源
ref.onDispose(() {
service.dispose();
});
return service;
});
// 需要时保持活跃
final cacheProvider = Provider.autoDispose<CacheService>((ref) {
final cache = CacheService();
// 在有数据时保持活跃
if (cache.hasData) {
ref.keepAlive();
}
return cache;
});
- 避免过度监听
// ❌ 错误:监听整个复杂状态
class MyWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final userState = ref.watch(userProvider); // 整个状态变化都会重建
return Text(userState.user?.name ?? '');
}
}
// ✅ 正确:只监听需要的部分
final userNameProvider = Provider<String?>((ref) {
return ref.watch(userProvider.select((state) => state.user?.name));
});
class MyWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final userName = ref.watch(userNameProvider); // 只有名字变化才重建
return Text(userName ?? '');
}
}
// 或者使用 select
class MyWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final userName = ref.watch(
userProvider.select((state) => state.user?.name)
);
return Text(userName ?? '');
}
}
- 正确使用 Family Provider
// ❌ 错误:Family Provider 参数过于复杂
final userProvider = StateNotifierProvider.family<UserNotifier, UserState, Map<String, dynamic>>((ref, params) {
return UserNotifier(params['id'], params['config']);
});
// ✅ 正确:使用简单参数或自定义类
class UserParams {
final String id;
final UserConfig config;
UserParams(this.id, this.config);
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is UserParams &&
runtimeType == other.runtimeType &&
id == other.id &&
config == other.config;
@override
int get hashCode => id.hashCode ^ config.hashCode;
}
final userProvider = StateNotifierProvider.family<UserNotifier, UserState, UserParams>((ref, params) {
return UserNotifier(params.id, params.config);
});
- 性能优化
// 使用 select 避免不必要的重建
final isLoadingProvider = Provider<bool>((ref) {
return ref.watch(userProvider.select((state) => state.isLoading));
});
// 使用 Consumer 局部重建
class UserProfile extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
// 静态内容不会重建
const Text('用户信息'),
// 只有这部分会根据状态变化重建
Consumer(
builder: (context, ref, child) {
final user = ref.watch(userProvider.select((state) => state.user));
return Text(user?.name ?? '未登录');
},
),
// 其他静态内容
const SizedBox(height: 20),
],
);
}
}
6. 总结
- 架构优势
- 清晰的分层结构: 每一层都有明确的职责,便于维护和测试
- 强类型安全: Riverpod 提供编译时检查,减少运行时错误
- 优秀的性能: 精确的重建控制,避免不必要的 UI 更新
- 易于测试: 依赖注入和状态隔离使测试变得简单
- 可扩展性: 模块化设计便于功能扩展和团队协作
- 最佳实践
- 遵循分层原则: 确保每层只处理自己的职责
- 合理使用 Provider: 根据需求选择合适的 Provider 类型
- 注意生命周期: 使用 autoDispose 避免内存泄漏
- 优化性能: 使用 select 和 Consumer 减少不必要的重建
- 统一错误处理: 在合适的层级处理和转换错误
- 编写测试: 利用 Riverpod 的测试友好特性编写单元测试
- 适用场景
- 中大型 Flutter 应用
- 需要复杂状态管理的应用
- 团队协作开发的项目
- 对性能和可维护性要求较高的应用
通过合理运用 Riverpod 和分层架构,可以构建出高质量、可维护、可测试的 Flutter 应用。关键是要理解每一层的职责,正确使用 Riverpod 的各种特性,并遵循最佳实践。