状态管理
状态管理是 Flutter 开发中的核心概念。一个良好的状态管理方案可以让应用更加可维护、可测试和可扩展。本章将深入介绍状态管理的基本概念、各种方案的原理和最佳实践。
什么是状态
状态是指在应用运行过程中可以改变的数据。理解状态的本质是掌握状态管理的关键。
状态的分类
根据状态的作用范围,可以将其分为两类:
1. 局部状态(Ephemeral State)
局部状态是只在单个 Widget 内部使用的状态,也称为"UI 状态"或"临时状态"。这类状态不需要跨 Widget 共享。
// 典型的局部状态示例
class CounterPage extends StatefulWidget {
@override
_CounterPageState createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int _counter = 0; // 局部状态
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text('计数:$_counter'),
),
floatingActionButton: FloatingActionButton(
onPressed: () => setState(() => _counter++),
child: Icon(Icons.add),
),
);
}
}
局部状态的特点:
- 使用
setState管理 - 状态与 Widget 生命周期绑定
- Widget 销毁时状态也随之消失
2. 应用状态(App State)
应用状态是需要跨多个 Widget 共享的状态,也称为"共享状态"。这类状态通常与业务逻辑相关。
// 典型的应用状态示例
- 用户登录状态
- 购物车内容
- 应用主题设置
- 多语言设置
- 网络连接状态
状态的另一个维度
根据状态的可变性,还可以分为:
- 可变状态:状态值可以被直接修改(如
List、Map) - 不可变状态:状态值一旦创建就不能修改,每次"修改"都会创建新实例
不可变状态的优势在于更容易追踪状态变化、避免意外的副作用,这也是许多现代状态管理方案采用的设计理念。
状态管理方案的演进
Flutter 的状态管理方案经历了多次演进。了解这些方案的原理有助于选择最适合项目需求的方案。
setState → InheritedWidget → Provider → Riverpod/Bloc/GetX
简单 底层机制 简化封装 完善生态
为什么需要状态管理方案?
考虑以下场景:用户登录后,多个页面需要显示用户信息。如果使用 setState,每个页面都需要单独管理用户状态,代码会变得混乱且难以维护。状态管理方案通过以下机制解决这个问题:
- 状态集中管理:将共享状态存储在统一的位置
- 响应式更新:当状态变化时,自动通知相关 UI 更新
- 关注点分离:将业务逻辑与 UI 层解耦
InheritedWidget
InheritedWidget 是 Flutter 内置的状态共享机制,许多状态管理库都基于它实现。理解 InheritedWidget 对于深入理解 Flutter 的状态管理至关重要。
工作原理
InheritedWidget 的核心思想是"依赖传递":子 Widget 可以向上查找并访问祖先 Widget 提供的数据。当数据变化时,依赖该数据的子 Widget 会被标记为需要重建。
Widget 树结构:
MaterialApp
│
InheritedWidget (提供数据)
│
┌───────┼───────┐
│ │ │
WidgetA WidgetB WidgetC
│ │
(依赖数据) (依赖数据)
当 InheritedWidget 的数据变化时,WidgetA 和 WidgetC 会重建
基本用法
import 'package:flutter/material.dart';
// 1. 创建 InheritedWidget
class CounterInherited extends InheritedWidget {
final int counter;
final VoidCallback increment;
final VoidCallback decrement;
const CounterInherited({
Key? key,
required this.counter,
required this.increment,
required this.decrement,
required Widget child,
}) : super(key: key, child: child);
// 提供便捷的静态访问方法
static CounterInherited? of(BuildContext context) {
// dependOnInheritedWidgetOfExactType 会注册依赖关系
// 当数据变化时,调用者会被通知重建
return context.dependOnInheritedWidgetOfExactType<CounterInherited>();
}
// 另一种方式:不建立依赖关系(用于一次性读取)
static CounterInherited? read(BuildContext context) {
// getElementForInheritedWidgetOfExactType 不会注册依赖
final element = context.getElementForInheritedWidgetOfExactType<CounterInherited>();
return element?.widget as CounterInherited?;
}
// 决定是否通知依赖者重建
@override
bool updateShouldNotify(CounterInherited oldWidget) {
return counter != oldWidget.counter;
}
}
// 2. 创建状态容器 Widget
class CounterProvider extends StatefulWidget {
final Widget child;
const CounterProvider({Key? key, required this.child}) : super(key: key);
@override
_CounterProviderState createState() => _CounterProviderState();
}
class _CounterProviderState extends State<CounterProvider> {
int _counter = 0;
void _increment() => setState(() => _counter++);
void _decrement() => setState(() => _counter--);
@override
Widget build(BuildContext context) {
return CounterInherited(
counter: _counter,
increment: _increment,
decrement: _decrement,
child: widget.child,
);
}
}
// 3. 在子 Widget 中使用
class CounterDisplay extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 会重建
final inherited = CounterInherited.of(context);
return Text('计数:${inherited?.counter ?? 0}');
}
}
class IncrementButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 不会重建(使用 read 方式)
final inherited = CounterInherited.read(context);
return ElevatedButton(
onPressed: inherited?.increment,
child: Text('增加'),
);
}
}
InheritedWidget 的局限性
虽然 InheritedWidget 是 Flutter 状态共享的基础,但直接使用它存在以下问题:
- 样板代码多:每次都需要创建新的 InheritedWidget 类
- 状态管理分散:业务逻辑和 UI 逻辑混在一起
- 缺乏调试工具:难以追踪状态变化
- API 不够友好:需要手动管理
updateShouldNotify
这就是为什么社区开发了 Provider、Riverpod 等库来简化状态管理。
Provider
Provider 是 Flutter 官方推荐的状态管理方案,它在 InheritedWidget 之上提供了更友好的 API。
核心概念
Provider 包含以下核心组件:
- Provider:提供值,但不支持更新通知
- ChangeNotifierProvider:提供
ChangeNotifier,当调用notifyListeners时自动通知依赖者 - ListenableProvider:更通用的监听器 Provider
- MultiProvider:一次性提供多个 Provider
添加依赖
在 pubspec.yaml 中添加:
dependencies:
provider: ^6.1.0
基本用法
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// 1. 创建状态类
class Counter with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners(); // 通知监听者
}
void decrement() {
_count--;
notifyListeners();
}
void reset() {
_count = 0;
notifyListeners();
}
}
// 2. 在应用顶层提供状态
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => Counter(),
child: MyApp(),
),
);
}
// 3. 使用状态
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Provider 示例')),
body: Center(
// watch:监听变化,当 Counter 变化时重建
child: Text(
'计数:${context.watch<Counter>().count}',
style: TextStyle(fontSize: 32),
),
),
floatingActionButton: FloatingActionButton(
// read:不监听变化,仅获取实例
onPressed: () => context.read<Counter>().increment(),
child: Icon(Icons.add),
),
);
}
}
Provider 与 ChangeNotifier 的关系
ChangeNotifierProvider
│
│ create
▼
Counter (ChangeNotifier)
│
│ notifyListeners()
▼
Consumer / context.watch
│
│ 重建
▼
Widget
Consumer 的精细控制
Consumer 允许在 Widget 树的特定位置重建,而不是整个 Widget:
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Consumer 示例')),
body: Column(
children: [
// 这部分不会重建
Text('这部分不会随计数变化而重建'),
SizedBox(height: 20),
// 只有这部分会重建
Consumer<Counter>(
builder: (context, counter, child) {
// child 是可选的,用于缓存不重建的部分
return Column(
children: [
child!, // 使用缓存的 child
Text(
'计数:${counter.count}',
style: TextStyle(fontSize: 32),
),
],
);
},
// 这部分只会构建一次
child: Text('这是缓存的 Widget'),
),
SizedBox(height: 20),
// 这部分也不会重建
Text('这部分也不会重建'),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () => context.read<Counter>().increment(),
child: Icon(Icons.add),
),
);
}
}
Selector 的选择性监听
当状态类有多个属性,但只想监听其中一个时,可以使用 Selector:
class User with ChangeNotifier {
String _name;
int _age;
String _email;
User({required String name, required int age, required String email})
: _name = name, _age = age, _email = email;
String get name => _name;
int get age => _age;
String get email => _email;
void updateName(String name) {
_name = name;
notifyListeners();
}
void updateAge(int age) {
_age = age;
notifyListeners();
}
void updateEmail(String email) {
_email = email;
notifyListeners();
}
}
// 使用 Selector 只监听 name 变化
class UserNameDisplay extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Selector<User, String>(
selector: (context, user) => user.name, // 只选择 name
shouldRebuild: (previous, next) => previous != next, // 决定是否重建
builder: (context, name, child) {
return Text('用户名:$name');
},
);
}
}
多个 Provider
使用 MultiProvider 同时提供多个状态:
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => User()),
ChangeNotifierProvider(create: (_) => Settings()),
ChangeNotifierProvider(create: (_) => Cart()),
// 依赖其他 Provider
ChangeNotifierProxyProvider<User, Auth>(
create: (_) => Auth(),
update: (context, user, auth) {
auth?.setUser(user);
return auth!;
},
),
],
child: MyApp(),
),
);
}
ProxyProvider:Provider 之间的依赖
当某个 Provider 需要依赖另一个 Provider 的值时:
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => AuthService()),
// AuthRepository 依赖 AuthService
ProxyProvider<AuthService, AuthRepository>(
update: (context, authService, previous) {
return AuthRepository(authService: authService);
},
),
// UserService 依赖 AuthRepository
ProxyProvider2<AuthService, AuthRepository, UserService>(
update: (context, authService, authRepository, previous) {
return UserService(authRepository: authRepository);
},
),
],
child: MyApp(),
),
);
}
完整示例:购物车
// 商品模型
class Product {
final String id;
final String name;
final double price;
Product({required this.id, required this.name, required this.price});
}
// 购物车状态
class Cart with ChangeNotifier {
final Map<String, int> _items = {}; // productId -> quantity
Map<String, int> get items => Map.unmodifiable(_items);
int get itemCount => _items.values.fold(0, (sum, qty) => sum + qty);
double get totalPrice {
// 这里需要结合 Product 数据计算
return _items.entries.fold(0.0, (sum, entry) {
// 实际应用中需要查找商品价格
return sum;
});
}
void addItem(String productId) {
_items[productId] = (_items[productId] ?? 0) + 1;
notifyListeners();
}
void removeItem(String productId) {
if (_items.containsKey(productId)) {
if (_items[productId]! > 1) {
_items[productId] = _items[productId]! - 1;
} else {
_items.remove(productId);
}
notifyListeners();
}
}
void clear() {
_items.clear();
notifyListeners();
}
}
// 应用
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => Cart(),
child: ShoppingCartApp(),
),
);
}
// 购物车页面
class CartPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('购物车'),
actions: [
// 使用 Selector 只监听数量变化
Selector<Cart, int>(
selector: (_, cart) => cart.itemCount,
builder: (context, count, _) {
return Stack(
alignment: Alignment.center,
children: [
Icon(Icons.shopping_cart),
if (count > 0)
Positioned(
right: 0,
top: 0,
child: Container(
padding: EdgeInsets.all(4),
decoration: BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
child: Text(
'$count',
style: TextStyle(color: Colors.white, fontSize: 10),
),
),
),
],
);
},
),
SizedBox(width: 16),
],
),
body: Consumer<Cart>(
builder: (context, cart, _) {
if (cart.items.isEmpty) {
return Center(child: Text('购物车是空的'));
}
return ListView.builder(
itemCount: cart.items.length,
itemBuilder: (context, index) {
final entry = cart.items.entries.elementAt(index);
return ListTile(
title: Text('商品 ${entry.key}'),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: Icon(Icons.remove),
onPressed: () => cart.removeItem(entry.key),
),
Text('${entry.value}'),
IconButton(
icon: Icon(Icons.add),
onPressed: () => cart.addItem(entry.key),
),
],
),
);
},
);
},
),
);
}
}
Riverpod
Riverpod 是 Provider 的进化版本,由同一位作者开发。它解决了 Provider 的一些根本性问题,提供了更安全、更灵活的状态管理方案。
Riverpod 的优势
相比 Provider,Riverpod 有以下改进:
| 特性 | Provider | Riverpod |
|---|---|---|
| 类型安全 | 运行时可能出错 | 编译时检查 |
| 不依赖 BuildContext | 必须有 context | 可以在任何地方访问 |
| 多个相同类型 Provider | 困难 | 自然支持 |
| 测试友好 | 需要 mock context | 直接覆盖 Provider |
| DevTools 支持 | 有限 | 完整的状态检查 |
添加依赖
dependencies:
flutter_riverpod: ^2.4.0
riverpod_annotation: ^2.3.0 # 可选,用于代码生成
dev_dependencies:
riverpod_generator: ^2.3.0 # 可选
build_runner: ^2.4.0
基本用法
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// 1. 定义 Provider(顶层声明)
final counterProvider = StateProvider<int>((ref) => 0);
// 2. 包装应用
void main() {
runApp(
ProviderScope(
child: MyApp(),
),
);
}
// 3. 使用 ConsumerWidget 或 ConsumerStatefulWidget
class CounterPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// watch:监听变化并重建
final counter = ref.watch(counterProvider);
return Scaffold(
body: Center(
child: Text('计数:$counter'),
),
floatingActionButton: FloatingActionButton(
// read:读取值但不监听
onPressed: () => ref.read(counterProvider.notifier).state++,
child: Icon(Icons.add),
),
);
}
}
Provider 类型详解
Riverpod 提供了多种 Provider 类型,每种都有特定的用途:
1. Provider(只读)
用于提供不会改变的值或计算值:
// 提供静态值
final apiUrlProvider = Provider<String>((ref) => 'https://api.example.com');
// 提供服务实例
final dioProvider = Provider<Dio>((ref) {
return Dio(BaseOptions(baseUrl: ref.watch(apiUrlProvider)));
});
// 计算值(依赖其他 Provider)
final doubledProvider = Provider<int>((ref) {
return ref.watch(counterProvider) * 2;
});
2. StateProvider(简单状态)
用于简单的状态管理,适合单个值:
// 简单计数
final counterProvider = StateProvider<int>((ref) => 0);
// 当前选中索引
final selectedIndexProvider = StateProvider<int>((ref) => 0);
// 文本输入
final searchQueryProvider = StateProvider<String>((ref) => '');
// 使用
class SearchBar extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final query = ref.watch(searchQueryProvider);
return TextField(
onChanged: (value) => ref.read(searchQueryProvider.notifier).state = value,
decoration: InputDecoration(hintText: '搜索...'),
);
}
}
3. StateNotifierProvider(复杂状态)
用于复杂的状态逻辑:
// 定义状态类
class Todo {
final String id;
final String title;
final bool completed;
Todo({
required this.id,
required this.title,
this.completed = false,
});
Todo copyWith({String? id, String? title, bool? completed}) {
return Todo(
id: id ?? this.id,
title: title ?? this.title,
completed: completed ?? this.completed,
);
}
}
// 定义 Notifier
class TodoListNotifier extends StateNotifier<List<Todo>> {
TodoListNotifier() : super([]);
void add(String title) {
state = [
...state,
Todo(id: DateTime.now().toString(), title: title),
];
}
void toggle(String id) {
state = state.map((todo) {
if (todo.id == id) {
return todo.copyWith(completed: !todo.completed);
}
return todo;
}).toList();
}
void remove(String id) {
state = state.where((todo) => todo.id != id).toList();
}
void clearCompleted() {
state = state.where((todo) => !todo.completed).toList();
}
}
// 定义 Provider
final todoListProvider = StateNotifierProvider<TodoListNotifier, List<Todo>>((ref) {
return TodoListNotifier();
});
// 使用
class TodoPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final todos = ref.watch(todoListProvider);
return Scaffold(
body: ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
final todo = todos[index];
return CheckboxListTile(
title: Text(todo.title),
value: todo.completed,
onChanged: (_) => ref.read(todoListProvider.notifier).toggle(todo.id),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(todoListProvider.notifier).add('新任务'),
child: Icon(Icons.add),
),
);
}
}
4. FutureProvider(异步数据)
用于一次性异步操作:
// 获取单个数据
final userProvider = FutureProvider<User>((ref) async {
final response = await dio.get('/user/1');
return User.fromJson(response.data);
});
// 使用
class UserPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final asyncUser = ref.watch(userProvider);
return asyncUser.when(
data: (user) => Text('用户:${user.name}'),
loading: () => CircularProgressIndicator(),
error: (error, stack) => Text('错误:$error'),
);
}
}
5. StreamProvider(流数据)
用于持续的数据流:
// 实时数据
final tickerProvider = StreamProvider<int>((ref) {
return Stream.periodic(Duration(seconds: 1), (count) => count);
});
// FirebaseAuth 状态
final authStateProvider = StreamProvider<User?>((ref) {
return FirebaseAuth.instance.authStateChanges();
});
// 使用
class TickerPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final asyncValue = ref.watch(tickerProvider);
return asyncValue.when(
data: (count) => Text('计数:$count'),
loading: () => CircularProgressIndicator(),
error: (error, stack) => Text('错误:$error'),
);
}
}
6. AsyncNotifierProvider(推荐的异步状态)
Riverpod 2.0 推荐使用 AsyncNotifier 处理异步状态:
class AsyncUserNotifier extends AsyncNotifier<User> {
@override
Future<User> build() async {
// 初始化时加载数据
final response = await dio.get('/user/1');
return User.fromJson(response.data);
}
Future<void> refresh() async {
state = const AsyncLoading();
state = await AsyncValue.guard(() async {
final response = await dio.get('/user/1');
return User.fromJson(response.data);
});
}
Future<void> updateName(String name) async {
state = const AsyncLoading();
state = await AsyncValue.guard(() async {
await dio.put('/user/1', data: {'name': name});
return state.value!.copyWith(name: name);
});
}
}
final userProvider = AsyncNotifierProvider<AsyncUserNotifier, User>(() {
return AsyncUserNotifier();
});
Provider 修饰符
family:参数化 Provider
允许根据参数创建不同的 Provider 实例:
// 使用 family 创建参数化 Provider
final userProvider = FutureProvider.family<User, int>((ref, userId) async {
final response = await dio.get('/users/$userId');
return User.fromJson(response.data);
});
// 使用
class UserDetail extends ConsumerWidget {
final int userId;
UserDetail({required this.userId});
@override
Widget build(BuildContext context, WidgetRef ref) {
// 传入参数
final asyncUser = ref.watch(userProvider(userId));
return asyncUser.when(
data: (user) => Text('用户:${user.name}'),
loading: () => CircularProgressIndicator(),
error: (error, stack) => Text('错误:$error'),
);
}
}
autoDispose:自动释放
当 Provider 不再被监听时自动重置:
// 使用 autoDispose 自动清理
final searchResultsProvider = FutureProvider.autoDispose<List<Product>>((ref) async {
final query = ref.watch(searchQueryProvider);
// 设置超时取消请求
final cancelToken = CancelToken();
ref.onDispose(cancelToken.cancel);
final response = await dio.get(
'/products/search',
queryParameters: {'q': query},
cancelToken: cancelToken,
);
return (response.data as List).map((e) => Product.fromJson(e)).toList();
});
Provider 组合
Riverpod 的强大之处在于 Provider 可以轻松组合:
// 基础 Provider
final userIdProvider = StateProvider<int>((ref) => 1);
// 用户数据
final userProvider = FutureProvider<User>((ref) async {
final userId = ref.watch(userIdProvider); // 监听 userId
final response = await dio.get('/users/$userId');
return User.fromJson(response.data);
});
// 用户文章
final userPostsProvider = FutureProvider<List<Post>>((ref) async {
final user = await ref.watch(userProvider.future); // 等待用户数据
final response = await dio.get('/users/${user.id}/posts');
return (response.data as List).map((e) => Post.fromJson(e)).toList();
});
// 过滤后的文章
final filteredPostsProvider = Provider<List<Post>>((ref) {
final posts = ref.watch(userPostsProvider).valueOrNull ?? [];
final filter = ref.watch(filterProvider);
if (filter.isEmpty) return posts;
return posts.where((p) => p.title.contains(filter)).toList();
});
Bloc(Business Logic Component)
Bloc 是一种基于事件驱动的状态管理模式,特别适合大型项目和团队协作。它强制将业务逻辑与 UI 完全分离。
核心概念
Bloc 模式的核心概念:
- Event:表示用户操作或系统事件
- State:表示 UI 的状态
- Bloc/Cubit:处理事件并产生新状态
用户操作 → Event → Bloc → State → UI 更新
Cubit vs Bloc
Bloc 库提供两种状态管理器:
| 特性 | Cubit | Bloc |
|---|---|---|
| 复杂度 | 简单 | 复杂 |
| API | 直接调用方法 | 发送事件 |
| 状态追踪 | 仅状态变化 | 事件+状态变化 |
| 适用场景 | 简单逻辑 | 复杂业务流程 |
添加依赖
dependencies:
flutter_bloc: ^8.1.0
equatable: ^2.0.0 # 用于状态比较
Cubit 示例
Cubit 是 Bloc 的简化版本,适合简单场景:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
// 1. 定义 Cubit
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
void reset() => emit(0);
}
// 2. 提供 Cubit
void main() {
runApp(
BlocProvider(
create: (_) => CounterCubit(),
child: MyApp(),
),
);
}
// 3. 使用 Cubit
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: BlocBuilder<CounterCubit, int>(
builder: (context, state) {
return Text('计数:$state', style: TextStyle(fontSize: 32));
},
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
heroTag: 'inc',
onPressed: () => context.read<CounterCubit>().increment(),
child: Icon(Icons.add),
),
SizedBox(height: 8),
FloatingActionButton(
heroTag: 'dec',
onPressed: () => context.read<CounterCubit>().decrement(),
child: Icon(Icons.remove),
),
],
),
);
}
}
Bloc 完整示例
对于复杂场景,使用完整的 Bloc 模式:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:equatable/equatable.dart';
// ========== 状态 ==========
abstract class CounterState extends Equatable {
final int count;
const CounterState(this.count);
@override
List<Object> get props => [count];
}
class CounterInitial extends CounterState {
const CounterInitial() : super(0);
}
class CounterValueChanged extends CounterState {
const CounterValueChanged(int count) : super(count);
}
class CounterLoading extends CounterState {
const CounterLoading(int count) : super(count);
}
// ========== 事件 ==========
abstract class CounterEvent extends Equatable {
const CounterEvent();
@override
List<Object> get props => [];
}
class CounterIncrementPressed extends CounterEvent {}
class CounterDecrementPressed extends CounterEvent {}
class CounterResetPressed extends CounterEvent {}
class CounterLoadFromStorage extends CounterEvent {}
// ========== Bloc ==========
class CounterBloc extends Bloc<CounterEvent, CounterState> {
final StorageService storageService;
CounterBloc({required this.storageService}) : super(const CounterInitial()) {
on<CounterIncrementPressed>(_onIncrement);
on<CounterDecrementPressed>(_onDecrement);
on<CounterResetPressed>(_onReset);
on<CounterLoadFromStorage>(_onLoadFromStorage);
}
Future<void> _onIncrement(
CounterIncrementPressed event,
Emitter<CounterState> emit,
) async {
emit(CounterValueChanged(state.count + 1));
await storageService.saveCount(state.count);
}
Future<void> _onDecrement(
CounterDecrementPressed event,
Emitter<CounterState> emit,
) async {
if (state.count > 0) {
emit(CounterValueChanged(state.count - 1));
await storageService.saveCount(state.count);
}
}
Future<void> _onReset(
CounterResetPressed event,
Emitter<CounterState> emit,
) async {
emit(const CounterValueChanged(0));
await storageService.saveCount(0);
}
Future<void> _onLoadFromStorage(
CounterLoadFromStorage event,
Emitter<CounterState> emit,
) async {
emit(CounterLoading(state.count));
final savedCount = await storageService.loadCount();
emit(CounterValueChanged(savedCount));
}
}
// ========== UI ==========
void main() {
runApp(
RepositoryProvider(
create: (_) => StorageService(),
child: BlocProvider(
create: (context) => CounterBloc(
storageService: context.read<StorageService>(),
)..add(CounterLoadFromStorage()),
child: MyApp(),
),
),
);
}
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Bloc 示例')),
body: Center(
child: BlocConsumer<CounterBloc, CounterState>(
listener: (context, state) {
// 处理副作用(如显示 Snackbar)
if (state is CounterLoading) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('加载中...')),
);
}
},
builder: (context, state) {
if (state is CounterLoading) {
return CircularProgressIndicator();
}
return Text(
'计数:${state.count}',
style: TextStyle(fontSize: 32),
);
},
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
heroTag: 'inc',
onPressed: () => context.read<CounterBloc>().add(CounterIncrementPressed()),
child: Icon(Icons.add),
),
SizedBox(height: 8),
FloatingActionButton(
heroTag: 'dec',
onPressed: () => context.read<CounterBloc>().add(CounterDecrementPressed()),
child: Icon(Icons.remove),
),
SizedBox(height: 8),
FloatingActionButton(
heroTag: 'reset',
onPressed: () => context.read<CounterBloc>().add(CounterResetPressed()),
child: Icon(Icons.refresh),
),
],
),
);
}
}
BlocBuilder 和 BlocListener
// BlocBuilder:仅重建 UI
BlocBuilder<CounterBloc, CounterState>(
buildWhen: (previous, current) {
// 条件重建
return previous.count != current.count;
},
builder: (context, state) {
return Text('${state.count}');
},
)
// BlocListener:处理副作用
BlocListener<CounterBloc, CounterState>(
listenWhen: (previous, current) {
// 条件监听
return current is CounterLoading;
},
listener: (context, state) {
// 导航、显示 Snackbar、显示 Dialog 等
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('状态变化')),
);
},
child: SomeWidget(),
)
// BlocConsumer:组合 Builder 和 Listener
BlocConsumer<CounterBloc, CounterState>(
listener: (context, state) { /* 副作用 */ },
builder: (context, state) { /* UI */ },
)
GetX
GetX 是一个全功能的 Flutter 框架,集成了状态管理、路由管理和依赖注入。它的特点是简单易用,代码量少。
添加依赖
dependencies:
get: ^4.6.0
三种状态管理方式
GetX 提供三种状态管理方式:
1. 简单状态管理(GetBuilder)
适合简单的状态更新:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
// 定义 Controller
class CounterController extends GetxController {
int count = 0;
void increment() {
count++;
update(); // 通知更新
}
void decrement() {
count--;
update();
}
}
// 使用
class CounterPage extends StatelessWidget {
final CounterController controller = Get.put(CounterController());
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GetBuilder<CounterController>(
builder: (_) => Text('计数:${controller.count}'),
),
),
floatingActionButton: FloatingActionButton(
onPressed: controller.increment,
child: Icon(Icons.add),
),
);
}
}
2. 响应式状态管理(Rx)
使用 .obs 创建响应式变量:
// 定义 Controller
class UserController extends GetxController {
// 响应式变量
final name = ''.obs;
final age = 0.obs;
final isLoggedIn = false.obs;
// 响应式列表
final items = <String>[].obs;
// 响应式 Map
final scores = <String, int>{}.obs;
void login(String userName, int userAge) {
name.value = userName;
age.value = userAge;
isLoggedIn.value = true;
}
void logout() {
isLoggedIn.value = false;
}
void addItem(String item) {
items.add(item);
}
}
// 使用 Obx 监听
class UserPage extends StatelessWidget {
final UserController controller = Get.put(UserController());
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
// Obx 自动监听所有使用的响应式变量
Obx(() => Text('用户名:${controller.name.value}')),
Obx(() => Text('年龄:${controller.age.value}')),
Obx(() => Text(
controller.isLoggedIn.value ? '已登录' : '未登录',
)),
// GetX Widget 可以指定具体 Controller
GetX<UserController>(
builder: (_) => Text('名字:${controller.name.value}'),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () => controller.login('张三', 25),
child: Icon(Icons.login),
),
);
}
}
3. GetBuilder + Rx 混合使用
class HybridController extends GetxController {
// 简单状态
int simpleCount = 0;
// 响应式状态
final reactiveCount = 0.obs;
void incrementSimple() {
simpleCount++;
update(); // 通知 GetBuilder
}
void incrementReactive() {
reactiveCount.value++; // 自动通知 Obx
}
}
依赖注入
GetX 提供了简单的依赖注入:
// 方式1:Get.put(立即创建)
final controller = Get.put(Controller());
// 方式2:Get.lazyPut(懒加载)
Get.lazyPut(() => Controller());
// 方式3:Get.putAsync(异步创建)
Get.putAsync(() async {
final service = await StorageService.getInstance();
return StorageController(service);
});
// 方式4:Get.create(每次获取创建新实例)
Get.create(() => Controller());
// 获取实例
final controller = Get.find<Controller>();
GetX 完整示例
import 'package:flutter/material.dart';
import 'package:get/get.dart';
// 模型
class Product {
final String id;
final String name;
final double price;
Product({required this.id, required this.name, required this.price});
}
// Controller
class ShopController extends GetxController {
// 响应式状态
final products = <Product>[].obs;
final cart = <String, int>{}.obs; // productId -> quantity
final isLoading = false.obs;
// 计算属性
int get cartItemCount => cart.values.fold(0, (sum, qty) => sum + qty);
double get cartTotal {
return cart.entries.fold(0.0, (sum, entry) {
final product = products.firstWhereOrNull((p) => p.id == entry.key);
return sum + (product?.price ?? 0) * entry.value;
});
}
@override
void onInit() {
super.onInit();
loadProducts();
}
Future<void> loadProducts() async {
isLoading.value = true;
await Future.delayed(Duration(seconds: 1));
products.value = [
Product(id: '1', name: '商品A', price: 100),
Product(id: '2', name: '商品B', price: 200),
Product(id: '3', name: '商品C', price: 300),
];
isLoading.value = false;
}
void addToCart(String productId) {
cart[productId] = (cart[productId] ?? 0) + 1;
}
void removeFromCart(String productId) {
if (cart[productId] != null && cart[productId]! > 0) {
cart[productId] = cart[productId]! - 1;
if (cart[productId] == 0) {
cart.remove(productId);
}
}
}
void clearCart() {
cart.clear();
}
}
// 页面
void main() {
runApp(GetMaterialApp(
home: ShopPage(),
));
}
class ShopPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 注入 Controller
final controller = Get.put(ShopController());
return Scaffold(
appBar: AppBar(
title: Text('商店'),
actions: [
// 使用 Obx 监听购物车变化
Obx(() {
final count = controller.cartItemCount;
return Stack(
alignment: Alignment.center,
children: [
Icon(Icons.shopping_cart),
if (count > 0)
Positioned(
right: 0,
top: 0,
child: Container(
padding: EdgeInsets.all(4),
decoration: BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
child: Text(
'$count',
style: TextStyle(color: Colors.white, fontSize: 10),
),
),
),
],
);
}),
SizedBox(width: 16),
],
),
body: Obx(() {
if (controller.isLoading.value) {
return Center(child: CircularProgressIndicator());
}
return ListView.builder(
itemCount: controller.products.length,
itemBuilder: (context, index) {
final product = controller.products[index];
final quantity = controller.cart[product.id] ?? 0;
return ListTile(
title: Text(product.name),
subtitle: Text('¥${product.price}'),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (quantity > 0) ...[
IconButton(
icon: Icon(Icons.remove),
onPressed: () => controller.removeFromCart(product.id),
),
Text('$quantity'),
],
IconButton(
icon: Icon(Icons.add),
onPressed: () => controller.addToCart(product.id),
),
],
),
);
},
);
}),
bottomNavigationBar: Obx(() {
return Container(
padding: EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('总计:¥${controller.cartTotal.toStringAsFixed(2)}'),
ElevatedButton(
onPressed: controller.cart.isEmpty ? null : () {
Get.snackbar('成功', '订单已提交');
controller.clearCart();
},
child: Text('结算'),
),
],
),
);
}),
);
}
}
状态管理方案对比
功能对比
| 特性 | Provider | Riverpod | Bloc | GetX |
|---|---|---|---|---|
| 学习曲线 | 低 | 中 | 高 | 低 |
| 类型安全 | 中 | 高 | 高 | 中 |
| 代码量 | 中 | 中 | 高 | 低 |
| 测试友好 | 中 | 高 | 高 | 中 |
| DevTools | 中 | 高 | 高 | 低 |
| 性能 | 高 | 高 | 高 | 高 |
| 社区支持 | 高 | 高 | 高 | 高 |
选择建议
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 小型项目 | Provider / GetX | 简单易用,快速开发 |
| 中型项目 | Riverpod | 类型安全,灵活 |
| 大型项目 | Bloc / Riverpod | 架构清晰,可维护性好 |
| 团队协作 | Bloc | 规范统一,易于 code review |
| 个人项目 | GetX / Riverpod | 开发效率高 |
最佳实践
1. 状态粒度设计
避免创建"上帝状态",按功能模块划分:
// 不好的做法:所有状态放一起
class AppState with ChangeNotifier {
User? user;
List<Product> products;
Cart cart;
Settings settings;
ThemeMode themeMode;
String searchQuery;
// ...几十个属性
}
// 好的做法:按功能模块划分
class UserState with ChangeNotifier { /* 用户相关 */ }
class CartState with ChangeNotifier { /* 购物车相关 */ }
class SettingsState with ChangeNotifier { /* 设置相关 */ }
2. 使用不可变状态
// 不好的做法:直接修改状态
class User {
String name;
int age;
}
void updateAge(User user, int newAge) {
user.age = newAge; // 直接修改
}
// 好的做法:使用不可变状态
class User {
final String name;
final int age;
User({required this.name, required this.age});
User copyWith({String? name, int? age}) {
return User(
name: name ?? this.name,
age: age ?? this.age,
);
}
}
void updateAge(User user, int newAge) {
final updatedUser = user.copyWith(age: newAge); // 创建新实例
}
3. 业务逻辑分离
将业务逻辑放在 Controller/Notifier 中,UI 只负责展示:
// 好的做法
class UserNotifier extends StateNotifier<AsyncValue<User>> {
final UserRepository _repository;
UserNotifier(this._repository) : super(const AsyncLoading()) {
loadUser();
}
Future<void> loadUser() async {
state = const AsyncLoading();
state = await AsyncValue.guard(() => _repository.getUser());
}
Future<void> updateName(String name) async {
state = const AsyncLoading();
state = await AsyncValue.guard(() async {
await _repository.updateName(name);
return state.value!.copyWith(name: name);
});
}
}
// UI 只负责展示
class UserPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final userState = ref.watch(userProvider);
return userState.when(
data: (user) => Text(user.name),
loading: () => CircularProgressIndicator(),
error: (e, s) => Text('错误:$e'),
);
}
}
4. 处理异步状态
使用统一的方式处理加载、成功、失败状态:
// 使用 AsyncValue (Riverpod)
final dataProvider = FutureProvider<Data>((ref) async {
return await api.fetchData();
});
// 使用
class DataPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final asyncData = ref.watch(dataProvider);
return asyncData.when(
data: (data) => DataView(data),
loading: () => LoadingIndicator(),
error: (error, stack) => ErrorView(error),
);
}
}
// 使用 Bloc 的状态
abstract class DataState {}
class DataInitial extends DataState {}
class DataLoading extends DataState {}
class DataLoaded extends DataState {
final Data data;
DataLoaded(this.data);
}
class DataError extends DataState {
final String message;
DataError(this.message);
}
5. 避免过度重建
使用 Selector 或条件重建:
// Provider: 使用 Selector
Selector<User, String>(
selector: (_, user) => user.name,
builder: (_, name, __) => Text(name),
)
// Riverpod: 使用 select
final name = ref.watch(userProvider.select((user) => user.name));
// Bloc: 使用 buildWhen
BlocBuilder<DataBloc, DataState>(
buildWhen: (previous, current) {
return previous.data != current.data;
},
builder: (context, state) => Text(state.data),
)
6. 正确处理生命周期
// 使用生命周期回调
class MyController extends GetxController {
late StreamSubscription _subscription;
@override
void onInit() {
super.onInit();
// 初始化
_subscription = stream.listen((data) {
// 处理数据
});
}
@override
void onClose() {
// 清理资源
_subscription.cancel();
super.onClose();
}
}
// Riverpod 使用 ref.onDispose
final provider = StreamProvider((ref) {
final controller = StreamController();
ref.onDispose(() {
controller.close();
});
return controller.stream;
});
小结
本章深入学习了 Flutter 状态管理:
- 状态分类:局部状态 vs 应用状态,可变状态 vs 不可变状态
- InheritedWidget:Flutter 内置的状态共享机制
- Provider:官方推荐,简单易用
- Riverpod:类型安全,灵活强大
- Bloc:事件驱动,架构清晰
- GetX:全功能框架,代码简洁
- 最佳实践:状态粒度、不可变状态、业务逻辑分离、异步处理
选择合适的状态管理方案取决于项目规模、团队能力和具体需求。建议先从 Provider 入门,逐步探索更高级的方案。
练习
- 使用 Provider 实现一个主题切换功能
- 使用 Riverpod 实现一个带缓存的用户数据加载
- 使用 Bloc 实现一个登录流程(包括表单验证、加载状态、错误处理)
- 使用 GetX 重构以上任意一个功能
- 比较不同状态管理方案的代码量和可读性