跳到主要内容

状态管理

状态管理是 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 共享的状态,也称为"共享状态"。这类状态通常与业务逻辑相关。

// 典型的应用状态示例
- 用户登录状态
- 购物车内容
- 应用主题设置
- 多语言设置
- 网络连接状态

状态的另一个维度

根据状态的可变性,还可以分为:

  • 可变状态:状态值可以被直接修改(如 ListMap
  • 不可变状态:状态值一旦创建就不能修改,每次"修改"都会创建新实例

不可变状态的优势在于更容易追踪状态变化、避免意外的副作用,这也是许多现代状态管理方案采用的设计理念。

状态管理方案的演进

Flutter 的状态管理方案经历了多次演进。了解这些方案的原理有助于选择最适合项目需求的方案。

 setState → InheritedWidget → Provider → Riverpod/Bloc/GetX
简单 底层机制 简化封装 完善生态

为什么需要状态管理方案?

考虑以下场景:用户登录后,多个页面需要显示用户信息。如果使用 setState,每个页面都需要单独管理用户状态,代码会变得混乱且难以维护。状态管理方案通过以下机制解决这个问题:

  1. 状态集中管理:将共享状态存储在统一的位置
  2. 响应式更新:当状态变化时,自动通知相关 UI 更新
  3. 关注点分离:将业务逻辑与 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 状态共享的基础,但直接使用它存在以下问题:

  1. 样板代码多:每次都需要创建新的 InheritedWidget 类
  2. 状态管理分散:业务逻辑和 UI 逻辑混在一起
  3. 缺乏调试工具:难以追踪状态变化
  4. 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 有以下改进:

特性ProviderRiverpod
类型安全运行时可能出错编译时检查
不依赖 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 库提供两种状态管理器:

特性CubitBloc
复杂度简单复杂
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('结算'),
),
],
),
);
}),
);
}
}

状态管理方案对比

功能对比

特性ProviderRiverpodBlocGetX
学习曲线
类型安全
代码量
测试友好
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 状态管理:

  1. 状态分类:局部状态 vs 应用状态,可变状态 vs 不可变状态
  2. InheritedWidget:Flutter 内置的状态共享机制
  3. Provider:官方推荐,简单易用
  4. Riverpod:类型安全,灵活强大
  5. Bloc:事件驱动,架构清晰
  6. GetX:全功能框架,代码简洁
  7. 最佳实践:状态粒度、不可变状态、业务逻辑分离、异步处理

选择合适的状态管理方案取决于项目规模、团队能力和具体需求。建议先从 Provider 入门,逐步探索更高级的方案。

练习

  1. 使用 Provider 实现一个主题切换功能
  2. 使用 Riverpod 实现一个带缓存的用户数据加载
  3. 使用 Bloc 实现一个登录流程(包括表单验证、加载状态、错误处理)
  4. 使用 GetX 重构以上任意一个功能
  5. 比较不同状态管理方案的代码量和可读性

参考资料