本篇文章主要介紹以下幾個(gè)知識(shí)點(diǎn):
- 分層架構(gòu)設(shè)計(jì)
- Provider vs Riverpod
- Riverpod 在實(shí)際項(xiàng)目中的使用示例

1. 分層架構(gòu)設(shè)計(jì)
Android 應(yīng)用架構(gòu)指南:https://developer.android.com/topic/architecture?hl=zh-cn
使用 Riverpod 的 Flutter 應(yīng)用架構(gòu)指南:https://codewithandrea.com/articles/flutter-app-architecture-riverpod-introduction/
1.1 Flutter 分層架構(gòu)概述

基于 Clean Architecture 原則,F(xiàn)lutter 應(yīng)用采用分層架構(gòu)確保關(guān)注點(diǎn)分離:
┌─────────────────────────────────────┐
│ Presentation Layer │ ← UI 層
├─────────────────────────────────────┤
│ Application Layer │ ← 業(yè)務(wù)邏輯層
├─────────────────────────────────────┤
│ Domain Layer │ ← 領(lǐng)域?qū)?├─────────────────────────────────────┤
│ Data Layer │ ← 數(shù)據(jù)層
└─────────────────────────────────────┘
主要層次包括:
-
data layer:(數(shù)據(jù)層)
- 職責(zé):數(shù)據(jù)獲取、存儲(chǔ)、網(wǎng)絡(luò)請(qǐng)求
- 組件:Repositories, Data Sources, APIs
- 特點(diǎn):處理數(shù)據(jù)的 CRUD 操作、抽象數(shù)據(jù)來(lái)源(網(wǎng)絡(luò)、本地?cái)?shù)據(jù)庫(kù))、數(shù)據(jù)轉(zhuǎn)換和緩存策略
-
domain layer:(領(lǐng)域?qū)樱?/p>
- 職責(zé):業(yè)務(wù)實(shí)體、業(yè)務(wù)規(guī)則(構(gòu)造model模型)
- 組件:Models, Entities, Value Objects
- 特點(diǎn):純 Dart 代碼(不依賴(lài) Flutter)、定義數(shù)據(jù)結(jié)構(gòu)和業(yè)務(wù)規(guī)則
-
application layer:(應(yīng)用層)
- 職責(zé):狀態(tài)管理、業(yè)務(wù)流程編排
- 組件:StateNotifier, Provider, Use Cases
- 特點(diǎn):協(xié)調(diào)不同領(lǐng)域服務(wù)、管理應(yīng)用狀態(tài)、處理用戶(hù)操作的業(yè)務(wù)邏輯
-
presentation layer:(表現(xiàn)層)
- 職責(zé):UI 組件、頁(yè)面、用戶(hù)交互
- 組件:Widgets, Pages, Dialogs
- 特點(diǎn):只負(fù)責(zé) UI 渲染和用戶(hù)交互、通過(guò) Riverpod Provider 獲取狀態(tài)、不包含業(yè)務(wù)邏輯
1.2 與 Android 架構(gòu)對(duì)比

| 層級(jí) | Flutter + Riverpod | Android MVVM |
|---|---|---|
| 表現(xiàn)層 | Widget + Consumer | View + Activity/Fragment |
| 應(yīng)用層 | StateNotifier + Provider | ViewModel + LiveData/Flow |
| 領(lǐng)域?qū)?/td> | Domain Models | Use Cases(網(wǎng)域?qū)樱蛇x) |
| 數(shù)據(jù)層 | Repository + DataSource | Repository + Room/Retrofit |
相似點(diǎn):
- 都遵循單向數(shù)據(jù)流
- 都有明確的層級(jí)分離
- 都使用觀察者模式進(jìn)行狀態(tài)管理
差異點(diǎn):
- Flutter 使用 Riverpod 的依賴(lài)注入更加簡(jiǎn)潔、響應(yīng)式編程更加直觀
- Android 領(lǐng)(網(wǎng))域?qū)邮强蛇x的,主要用于封裝復(fù)雜業(yè)務(wù)邏輯或多個(gè) ViewModel 共享的業(yè)務(wù)邏輯,對(duì)應(yīng) Flutter 中的應(yīng)用層(項(xiàng)目中采用了 Riverpod 自動(dòng)生成代碼的方式時(shí),也可以省略)。
- Flutter 的領(lǐng)域?qū)痈嘤糜诙x數(shù)據(jù)模型和業(yè)務(wù)實(shí)體(只針對(duì)本文的架構(gòu))
2. Provider vs Riverpod
Provider 和 Riverpod 這兩個(gè)庫(kù)的作者都是 Remi Rousselet,新庫(kù)命名是舊庫(kù)的字母重排。
Provider 的優(yōu)點(diǎn)是 簡(jiǎn)單易用,上手難度低,適用于應(yīng)用規(guī)模較小,狀態(tài)管理不太復(fù)雜的場(chǎng)景。
Provider 的局限如下:
依賴(lài)
BuildContext。
Provider 是基于InheritedWidget封裝,讀取狀態(tài)需要BuildContext,所以 只能在Widget樹(shù)中聲明使用。
而在有些場(chǎng)景下不一定能直接拿到BuildContext,如在 非UI層 (如業(yè)務(wù)邏輯層) 訪問(wèn)狀態(tài),只能通過(guò)某種方式傳遞BuildContext實(shí)例,繁瑣之余還增加了代碼的耦合度。
使用不當(dāng),還可能導(dǎo)致ProviderNotFoundException。多個(gè)相同類(lèi)型的 Provider,需要自己維護(hù)一個(gè)
Key進(jìn)行區(qū)分。
如:Widget 樹(shù)的同一層級(jí),為相同類(lèi)型的狀態(tài)創(chuàng)建多個(gè)同類(lèi)型的 Provider,子 Widget 無(wú)法確定使用哪個(gè) Provider 的數(shù)據(jù),需要指定一個(gè)特定的Key來(lái)進(jìn)行區(qū)分:
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Counter(1), key: ValueKey(1)),
ChangeNotifierProvider(create: (_) => Counter(2), key: ValueKey(2)),
],
child: MyApp(),
),
);
}
// 通過(guò)key指定使用Counter實(shí)例
Provider.of<Counter>(context, listen: false, key: ValueKey(1)).increment();
- 如果需要跨 Widget 共享狀態(tài),Provider 就沒(méi)法弄成局部私有的,只能是全局可訪問(wèn)的。
Riverpod 在 Provider 的基礎(chǔ)上進(jìn)行重構(gòu),解決上述問(wèn)題之余,提供了 更靈活/精細(xì)的狀態(tài)管理機(jī)制,狀態(tài)不可變,編譯時(shí)類(lèi)型安全、易于測(cè)試等特性,更清晰的代碼組織和維護(hù)方式 (注解代碼生成),可以有效的組織和管理大規(guī)模的狀態(tài)。
選擇 Riverpod 的理由:
- 類(lèi)型安全: 編譯時(shí)檢查,減少運(yùn)行時(shí)錯(cuò)誤
- 更好的性能: 精確的重建控制,避免不必要的 Widget 重建
- 簡(jiǎn)化的 API: 更直觀的語(yǔ)法,減少樣板代碼
- 強(qiáng)大的開(kāi)發(fā)工具: 更好的調(diào)試和開(kāi)發(fā)體驗(yàn)
- 測(cè)試友好: 更容易進(jìn)行單元測(cè)試和集成測(cè)試
3. Riverpod 基本使用與核心原理
3.1 Riverpod 基本使用
Riverpod 使用詳解可參考:https://juejin.cn/post/7359402114018689076
官方文檔:https://riverpod.dev/docs/introduction/why_riverpod
Riverpod 提供了多種狀態(tài)管理模式,適用于不同的場(chǎng)景:
| 模式 | 適用場(chǎng)景 | 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|---|---|
| StateProvider | 簡(jiǎn)單狀態(tài) | 語(yǔ)法簡(jiǎn)單,直接修改 | 不適合復(fù)雜邏輯 |
| StateNotifier | 復(fù)雜狀態(tài) | 強(qiáng)類(lèi)型,業(yè)務(wù)邏輯封裝好 | 需要更多代碼 |
| FutureProvider | 一次性異步 | 自動(dòng)處理加載狀態(tài) | 不適合可變異步操作 |
| StreamProvider | 持續(xù)數(shù)據(jù)流 | 自動(dòng)處理流狀態(tài) | 需要管理流的生命周期 |
| ChangeNotifier | 兼容舊代碼 | 兼容性好 | 性能相對(duì)較差 |
| Notifier | 現(xiàn)代化狀態(tài)管理 | 語(yǔ)法簡(jiǎn)潔,性能好 | 需要代碼生成 |
3.2 Riverpod 核心原理
把應(yīng)用的“數(shù)據(jù)源”和“依賴(lài)關(guān)系”想象成一張河網(wǎng):上游的水質(zhì)變化,會(huì)沿著支流層層傳導(dǎo)到下游。
Riverpod 做的事情,就是把這張“依賴(lài)河網(wǎng)”用代碼表達(dá)出來(lái),并且自動(dòng)完成“緩存、傳導(dǎo)、重算、清理”。
這讓復(fù)雜異步場(chǎng)景變得簡(jiǎn)單,正如官方所說(shuō):它是一個(gè)“響應(yīng)式緩存框架”,專(zhuān)注于緩存與自動(dòng)刷新。
-
核心對(duì)象
- ProviderContainer:一座“水庫(kù)”,保存所有 provider 的緩存與依賴(lài)圖,支持覆蓋與作用域。
- Provider/Family:一段“取水邏輯”的聲明,描述 value 如何被計(jì)算,是否帶參數(shù)(family)。
- Ref(WidgetRef/Ref):提供讀依賴(lài)、注冊(cè)監(jiān)聽(tīng)、生命周期回調(diào)(onDispose/keepAlive)的“水工”。
- Listener:下游訂閱者,只有當(dāng)感興趣的上游發(fā)生變化時(shí)才會(huì)被通知(select 精準(zhǔn)過(guò)濾)。
一個(gè)極簡(jiǎn)(偽)實(shí)現(xiàn):
// 極簡(jiǎn)容器:保存緩存與依賴(lài)
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;
}
// 當(dāng)依賴(lài)變更時(shí),向下游傳播“需要重算”的信號(hào)
void markDirty(Object changed) {
for (final entry in _deps.entries) {
if (entry.value.contains(changed)) {
_cache.remove(entry.key); // 使下游失效,下一次 read 時(shí)重算
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);
}
上面?zhèn)未a展示了 Riverpod 的核心:
- read 第一次會(huì)計(jì)算并緩存 value;
- watch 讓容器記錄“誰(shuí)依賴(lài)了誰(shuí)”;
- 當(dāng)上游變化,容器遞歸失效下游緩存;
- 下游在下次被讀取/監(jiān)聽(tīng)時(shí)自動(dòng)重算。
4. 工作流實(shí)現(xiàn)
下面展示在實(shí)際項(xiàng)目中的工作流:
- 模塊目錄結(jié)構(gòu)
lib/
├── api/
│ └── api_service.dart # api 相關(guān)
|
├── features/
│ ├── air/
│ ├── application/ # 應(yīng)用層
│ │ └── providers.dart
│ ├── data/ # 數(shù)據(jù)層
│ │ └── history_repository.dart # 數(shù)據(jù)倉(cāng)庫(kù)
│ ├── domain/ # 領(lǐng)域?qū)?│ │ ├── history.dart # 數(shù)據(jù)模型
│ │ └── history.g.dart
│ └── presentation/ # 表現(xiàn)層
│ ├── widget/ # 通用組件
│ │ └── chart_bar.dart
| ├── dialog/ # 對(duì)話框組件
│ └── statistics_page.dart # 統(tǒng)計(jì)UI頁(yè)面
- 定義 API 接口
/// lib/api/api.dart
class Api {
static const String _path = '/dev/v1/';
// 獲取指定時(shí)間段的歷史數(shù)據(jù):/user/nodes/tsdata
static const String historyData = "${_path}user/nodes/tsdata";
}
/// lib/api/api_service.dart
class ApiService {
// 獲取24小時(shí)平均值
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;
}
}
- 定義數(shù)據(jù)倉(cāng)庫(kù)(data layer)
/// lib/features/air/data/history_repository.dart
class HistoryRepository {
final ApiService apiService;
HistoryRepository({required this.apiService});
// 獲取24小時(shí)平均值
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());
});
- 定義數(shù)據(jù)模型(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);
}
- 實(shí)現(xiàn)狀態(tài)管理(application layer)
/// 狀態(tài)定義
class AirState {
final AsyncValue<History> data;
const AirState({
this.data = const AsyncLoading(),
});
AirState copyWith({AsyncValue<History>? data}) {
return AirState(data: data ?? this.data);
}
}
/// 狀態(tài)管理器
class AirNotifier extends StateNotifier<AirState> {
final HistoryRepository _repository;
final String _nodeId;
AirNotifier(this._repository, this._nodeId) : super(const AirState());
// 獲取歷史數(shù)據(jù)
Future<History> getHistoryData() async {
final result = _repository.get24hoursData(_nodeId);
if (mounted) {
state = state.copyWith(data: result);
}
return result;
}
}
// 歷史數(shù)據(jù)狀態(tài)管理器 provider
final airNotifierProvider = StateNotifierProvider.autoDispose
.family<AirNotifier, AirState, String>((ref, nodeId) {
final repository = ref.watch(historyRepositoryProvider);
return AirNotifier(repository, nodeId);
});
- UI 層實(shí)現(xiàn)(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'),
);
}
}
小結(jié):
這個(gè)工作流展示了完整的數(shù)據(jù)流向:
- API 定義 → 定義網(wǎng)絡(luò)請(qǐng)求接口
- Repository 實(shí)現(xiàn) → 處理數(shù)據(jù)獲取、緩存、錯(cuò)誤處理
- Domain 模型 → 定義業(yè)務(wù)數(shù)據(jù)結(jié)構(gòu)
- StateNotifier → 管理復(fù)雜業(yè)務(wù)狀態(tài)
- Provider 定義 → 提供依賴(lài)注入和狀態(tài)訪問(wèn)
- UI 消費(fèi) → 響應(yīng)式 UI 更新
數(shù)據(jù)流向:
UI (Consumer)
↓ ref.watch()
Provider
↓ StateNotifier
Application Layer
↓ Repository
Data Layer
↓ API/Database
External Data Source
5. 注意事項(xiàng)
- Provider 生命周期管理
// ? 錯(cuò)誤:不必要的長(zhǎng)期持有
final expensiveProvider = Provider<ExpensiveService>((ref) {
return ExpensiveService(); // 會(huì)一直存在內(nèi)存中
});
// ? 正確:使用 autoDispose
final expensiveProvider = Provider.autoDispose<ExpensiveService>((ref) {
final service = ExpensiveService();
// 清理資源
ref.onDispose(() {
service.dispose();
});
return service;
});
// 需要時(shí)保持活躍
final cacheProvider = Provider.autoDispose<CacheService>((ref) {
final cache = CacheService();
// 在有數(shù)據(jù)時(shí)保持活躍
if (cache.hasData) {
ref.keepAlive();
}
return cache;
});
- 避免過(guò)度監(jiān)聽(tīng)
// ? 錯(cuò)誤:監(jiān)聽(tīng)整個(gè)復(fù)雜狀態(tài)
class MyWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final userState = ref.watch(userProvider); // 整個(gè)狀態(tài)變化都會(huì)重建
return Text(userState.user?.name ?? '');
}
}
// ? 正確:只監(jiān)聽(tīng)需要的部分
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
// ? 錯(cuò)誤:Family Provider 參數(shù)過(guò)于復(fù)雜
final userProvider = StateNotifierProvider.family<UserNotifier, UserState, Map<String, dynamic>>((ref, params) {
return UserNotifier(params['id'], params['config']);
});
// ? 正確:使用簡(jiǎn)單參數(shù)或自定義類(lèi)
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);
});
- 性能優(yōu)化
// 使用 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: [
// 靜態(tài)內(nèi)容不會(huì)重建
const Text('用戶(hù)信息'),
// 只有這部分會(huì)根據(jù)狀態(tài)變化重建
Consumer(
builder: (context, ref, child) {
final user = ref.watch(userProvider.select((state) => state.user));
return Text(user?.name ?? '未登錄');
},
),
// 其他靜態(tài)內(nèi)容
const SizedBox(height: 20),
],
);
}
}
6. 總結(jié)
- 架構(gòu)優(yōu)勢(shì)
- 清晰的分層結(jié)構(gòu): 每一層都有明確的職責(zé),便于維護(hù)和測(cè)試
- 強(qiáng)類(lèi)型安全: Riverpod 提供編譯時(shí)檢查,減少運(yùn)行時(shí)錯(cuò)誤
- 優(yōu)秀的性能: 精確的重建控制,避免不必要的 UI 更新
- 易于測(cè)試: 依賴(lài)注入和狀態(tài)隔離使測(cè)試變得簡(jiǎn)單
- 可擴(kuò)展性: 模塊化設(shè)計(jì)便于功能擴(kuò)展和團(tuán)隊(duì)協(xié)作
- 最佳實(shí)踐
- 遵循分層原則: 確保每層只處理自己的職責(zé)
- 合理使用 Provider: 根據(jù)需求選擇合適的 Provider 類(lèi)型
- 注意生命周期: 使用 autoDispose 避免內(nèi)存泄漏
- 優(yōu)化性能: 使用 select 和 Consumer 減少不必要的重建
- 統(tǒng)一錯(cuò)誤處理: 在合適的層級(jí)處理和轉(zhuǎn)換錯(cuò)誤
- 編寫(xiě)測(cè)試: 利用 Riverpod 的測(cè)試友好特性編寫(xiě)單元測(cè)試
- 適用場(chǎng)景
- 中大型 Flutter 應(yīng)用
- 需要復(fù)雜狀態(tài)管理的應(yīng)用
- 團(tuán)隊(duì)協(xié)作開(kāi)發(fā)的項(xiàng)目
- 對(duì)性能和可維護(hù)性要求較高的應(yīng)用
通過(guò)合理運(yùn)用 Riverpod 和分層架構(gòu),可以構(gòu)建出高質(zhì)量、可維護(hù)、可測(cè)試的 Flutter 應(yīng)用。關(guān)鍵是要理解每一層的職責(zé),正確使用 Riverpod 的各種特性,并遵循最佳實(shí)踐。