Flutter 状态管理
运行代码生成: AsyncValue 常用方法: 在 build 方法中错误地使用 ref.read 会导致状态变化后 UI 不更新: 小组件用 StatefulWidget,应用级状态用 Riverpod:setState 适合局部 UI 状态(按钮选中、展开收起),跨页面共享状态改用 Riverpod 的 Provider/AsyncNotifier,避免 prop drilling。 Riverpod 优先用 AsyncNotifierProvider:异步数据(API 调用)统一用 AsyncNotifierProvider,自动处理 loadi
官方文档:https://docs.flutter.dev/data-and-backend/state-mgmt/intro
适用版本:Flutter 3.x(2026-05-07 核实)
状态管理方案对比
| 方案 | 学习曲线 | 适用规模 | 测试友好度 | 备注 |
|---|---|---|---|---|
| setState | 低 | 小型/组件级 | 一般 | Flutter 内置,无需依赖 |
| Provider | 低-中 | 小型-中型 | 良好 | 官方推荐入门方案 |
| Riverpod | 中 | 中型-大型 | 优秀 | Provider 的改进版,推荐 |
| Bloc | 中-高 | 中型-大型 | 优秀 | 事件驱动,结构严格 |
| GetX | 低 | 中小型 | 较差 | 功能大而全,侵入性强 |
Riverpod(推荐方案)
安装
# pubspec.yaml
dependencies:
flutter_riverpod: ^2.5.1
riverpod_annotation: ^2.3.5
dev_dependencies:
build_runner: ^2.4.9
riverpod_generator: ^2.4.0
Provider 类型
| Provider 类型 | 用途 | 返回值特征 |
|---|---|---|
Provider |
只读依赖、常量计算值 | 不可变 |
StateProvider |
简单状态(计数器、开关等) | 可通过 ref.read().state = x 修改 |
StateNotifierProvider |
复杂状态,Riverpod 1.x 推荐 | StateNotifier 子类 |
FutureProvider |
单次异步请求 | AsyncValue<T> |
StreamProvider |
持续监听的数据流 | AsyncValue<T> |
NotifierProvider |
复杂状态,Riverpod 2.x 推荐 | Notifier 子类 |
ref.watch / ref.read / ref.listen 区别
| 方法 | 触发重建 | 使用场景 |
|---|---|---|
ref.watch(provider) |
是 | Widget build 方法内,监听状态变化 |
ref.read(provider) |
否 | 事件回调内,只需读取一次或调用方法 |
ref.listen(provider, callback) |
否(手动处理) | 监听变化并执行副作用(导航、弹窗等) |
StateNotifier + StateNotifierProvider 示例
import 'package:flutter_riverpod/flutter_riverpod.dart';
// 状态类
class CounterState {
final int count;
final bool isLoading;
const CounterState({required this.count, this.isLoading = false});
CounterState copyWith({int? count, bool? isLoading}) {
return CounterState(
count: count ?? this.count,
isLoading: isLoading ?? this.isLoading,
);
}
}
// StateNotifier
class CounterNotifier extends StateNotifier<CounterState> {
CounterNotifier() : super(const CounterState(count: 0));
void increment() {
state = state.copyWith(count: state.count + 1);
}
void decrement() {
state = state.copyWith(count: state.count - 1);
}
Future<void> fetchFromServer() async {
state = state.copyWith(isLoading: true);
await Future.delayed(const Duration(seconds: 1));
state = state.copyWith(count: 42, isLoading: false);
}
}
// Provider 定义
final counterProvider =
StateNotifierProvider<CounterNotifier, CounterState>((ref) {
return CounterNotifier();
});
// 使用
class CounterWidget extends ConsumerWidget {
const CounterWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(counterProvider);
return Column(
children: [
Text('Count: ${state.count}'),
ElevatedButton(
onPressed: () => ref.read(counterProvider.notifier).increment(),
child: const Text('Increment'),
),
],
);
}
}
Notifier + NotifierProvider(Riverpod 2.x 新 API)
import 'package:flutter_riverpod/flutter_riverpod.dart';
class CounterNotifier2 extends Notifier<int> {
@override
int build() => 0; // 初始状态
void increment() => state++;
void decrement() => state--;
}
final counterProvider2 = NotifierProvider<CounterNotifier2, int>(() {
return CounterNotifier2();
});
代码生成:@riverpod 注解
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'counter.g.dart';
@riverpod
class Counter extends _$Counter {
@override
int build() => 0;
void increment() => state++;
}
// 异步 Provider
@riverpod
Future<List<String>> userList(UserListRef ref) async {
final response = await http.get(Uri.parse('https://api.example.com/users'));
return parseUsers(response.body);
}
运行代码生成:
dart run build_runner build --delete-conflicting-outputs
AsyncValue 处理异步状态
@riverpod
Future<User> userDetail(UserDetailRef ref, int id) async {
return await apiClient.getUser(id);
}
// 在 Widget 中使用
class UserDetailWidget extends ConsumerWidget {
final int userId;
const UserDetailWidget({super.key, required this.userId});
@override
Widget build(BuildContext context, WidgetRef ref) {
final asyncUser = ref.watch(userDetailProvider(userId));
return asyncUser.when(
data: (user) => Text(user.name),
loading: () => const CircularProgressIndicator(),
error: (error, stack) => Text('Error: $error'),
);
}
}
AsyncValue 常用方法:
| 方法/属性 | 说明 |
|---|---|
when(data:, loading:, error:) |
分支处理三种状态 |
maybeWhen(...) |
仅处理部分分支,其余走 orElse |
whenData((data) => ...) |
仅转换 data,保留 loading/error |
value |
获取数据,可能为 null |
hasValue |
是否有数据(包括刷新中) |
isLoading |
是否正在加载 |
hasError |
是否有错误 |
ref.invalidate() 刷新 Provider
// 强制重新执行 FutureProvider 或 StreamProvider
ref.invalidate(userListProvider);
// 带参数的 Provider
ref.invalidate(userDetailProvider(userId));
ProviderScope 覆盖(测试场景)
// 测试中覆盖 Provider
testWidgets('shows user name', (tester) async {
await tester.pumpWidget(
ProviderScope(
overrides: [
userDetailProvider(1).overrideWith((ref) async => User(name: 'Test')),
],
child: const MaterialApp(home: UserDetailWidget(userId: 1)),
),
);
expect(find.text('Test'), findsOneWidget);
});
Bloc
Event / State / Bloc 三层结构
// Event
abstract class CounterEvent {}
class CounterIncremented extends CounterEvent {}
class CounterDecremented extends CounterEvent {}
// State
class CounterState {
final int count;
const CounterState(this.count);
}
// Bloc
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(const CounterState(0)) {
on<CounterIncremented>((event, emit) {
emit(CounterState(state.count + 1));
});
on<CounterDecremented>((event, emit) {
emit(CounterState(state.count - 1));
});
}
}
BlocProvider / BlocBuilder / BlocListener / BlocConsumer
| Widget/方法 | 参数 | 说明 |
|---|---|---|
BlocProvider |
create, child, lazy |
创建并向下注入 Bloc |
BlocBuilder |
bloc, buildWhen, builder |
监听状态并重建 UI |
BlocListener |
bloc, listenWhen, listener |
监听状态执行副作用,不重建 |
BlocConsumer |
bloc, buildWhen, listenWhen, builder, listener |
同时具备 Builder 和 Listener |
// 提供 Bloc
BlocProvider(
create: (context) => CounterBloc(),
child: const CounterView(),
)
// 重建 UI
BlocBuilder<CounterBloc, CounterState>(
buildWhen: (previous, current) => previous.count != current.count,
builder: (context, state) => Text('${state.count}'),
)
// 副作用(导航、弹窗)
BlocListener<CounterBloc, CounterState>(
listenWhen: (previous, current) => current.count == 10,
listener: (context, state) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Reached 10!')),
);
},
child: const CounterView(),
)
Cubit(简化版 Bloc)
// 无 Event,直接调用方法修改状态
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}
context.read vs context.watch
| 方法 | 触发重建 | 使用场景 |
|---|---|---|
context.read<MyBloc>() |
否 | 事件回调中触发 Bloc 事件 |
context.watch<MyBloc>() |
是 | build 方法中监听状态 |
// 事件回调中用 read
ElevatedButton(
onPressed: () => context.read<CounterBloc>().add(CounterIncremented()),
child: const Text('+'),
)
// build 中用 watch
Text('${context.watch<CounterBloc>().state.count}')
Bloc 测试
import 'package:bloc_test/bloc_test.dart';
void main() {
group('CounterBloc', () {
late CounterBloc bloc;
setUp(() => bloc = CounterBloc());
tearDown(() => bloc.close());
blocTest<CounterBloc, CounterState>(
'emits [CounterState(1)] when CounterIncremented is added',
build: () => CounterBloc(),
act: (bloc) => bloc.add(CounterIncremented()),
expect: () => [const CounterState(1)],
);
});
}
踩坑与注意事项
Riverpod 中 ref.read 不触发重建
在 build 方法中错误地使用 ref.read 会导致状态变化后 UI 不更新:
// 错误:ref.read 不监听变化
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.read(counterProvider); // 只读一次,不更新
return Text('$count');
}
// 正确:在 build 中使用 ref.watch
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Text('$count');
}
Riverpod 中 Provider 不可在 build 之外使用 ref.watch
// 错误:在回调中使用 ref.watch
ElevatedButton(
onPressed: () {
final value = ref.watch(someProvider); // 会抛出异常
},
child: const Text('OK'),
)
// 正确:回调中用 ref.read
ElevatedButton(
onPressed: () {
final value = ref.read(someProvider);
},
child: const Text('OK'),
)
Bloc 中 emit 不可在 close 后调用
class MyBloc extends Bloc<MyEvent, MyState> {
MyBloc() : super(MyInitial()) {
on<LoadData>((event, emit) async {
// 如果 Bloc 在请求返回前被关闭,emit 会抛出 StateError
final data = await repository.fetchData();
// 需要检查是否已关闭
if (!isClosed) {
emit(MyLoaded(data));
}
});
}
}
Riverpod ProviderScope 必须在 runApp 最外层
void main() {
runApp(
const ProviderScope( // 必须包裹整个应用
child: MyApp(),
),
);
}
最佳实践
小组件用 StatefulWidget,应用级状态用 Riverpod:setState 适合局部 UI 状态(按钮选中、展开收起),跨页面共享状态改用 Riverpod 的 Provider/AsyncNotifier,避免 prop drilling。
Riverpod 优先用 AsyncNotifierProvider:异步数据(API 调用)统一用 AsyncNotifierProvider,自动处理 loading/error/data 三态,配合 ref.watch 在 UI 层声明式消费:
@riverpod
class UserList extends _$UserList {
@override
Future<List<User>> build() => fetchUsers();
Future<void> refresh() => ref.refresh(userListProvider.future);
}
Bloc 场景下 Event → State 单向数据流:所有状态变更通过 add(Event) 触发,禁止在 Bloc 外部直接修改 State,保持可测试性和可追踪性。
避免在 Provider 构建函数中执行副作用:build() 方法可能被多次调用,副作用(网络请求、写文件)应放在 AsyncNotifier.build 里(返回 Future),而不是构造函数。
ref.listen 替代命令式导航:在 Riverpod 中监听状态变化后触发导航,比 StreamBuilder 更简洁,避免在 build 里写命令式逻辑:
ref.listen(authProvider, (prev, next) {
if (next.isLoggedOut) context.go('/login');
});
常见陷阱
陷阱:Riverpod ref.watch 在非 build 方法中调用
现象: 在事件处理函数或 initState 中调用 ref.watch,Provider 不更新或报断言错误。
原因: ref.watch 只在 build 方法中有效,非构建期调用不会建立订阅关系。
解决: 非 build 期读取用 ref.read,监听变化用 ref.listen。
陷阱:Bloc emit 相同 State 不触发重建
现象: emit(state.copyWith(count: count)) 后 UI 没有更新。
原因: Bloc 默认用 == 比较新旧 State,若引用或值相同则不会触发 rebuild。
解决: 确保 State 实现 Equatable 且 props 包含所有可变字段,或每次 emit 新实例而非同一引用。
陷阱:全局 Provider 持有 BuildContext 导致内存泄漏
现象: 页面销毁后 Provider 仍存在,持有的 context 引用导致整棵 Widget 树无法 GC。
原因: 在 Provider 的构造函数或 build() 中捕获了 context,而 Provider 生命周期比 Widget 长。
解决: Provider 层禁止持有 context,UI 交互回调通过 ref.read 获取服务后执行操作。