6.1 什么是状态管理?
生活中的例子
想象你和室友们共用一个冰箱:
- 没有状态管理:每个人自己记冰箱里有什么,有人买了牛奶但没告诉别人,结果大家都买了牛奶
- 有状态管理:冰箱上贴一张清单,任何人拿放东西都更新清单,大家看清单就知道冰箱里有什么
状态管理就是让应用中的数据(状态)能够被需要的地方访问和更新。
为什么需要状态管理?
在 Flutter 中,Widget 树是层级结构:
MyApp
└── HomeScreen
├── AppBar
│ └── Title (需要显示任务数量)
├── TaskList
│ ├── TaskItem (需要显示任务)
│ └── TaskItem
└── FloatingActionButton (需要添加任务)问题: 任务列表数据在 TaskList 中,但 AppBar 和 FloatingActionButton 也需要访问和修改这些数据。
解决方案: 使用状态管理库(Provider)将数据放在树的上层,让所有子组件都能访问。
6.2 Provider 简介
什么是 Provider?
Provider 是 Flutter 官方推荐的状态管理方案。它的核心思想是:
将数据放在树的上方,让所有需要的地方都能获取。
Provider 的工作原理
┌─────────────────────────────────────┐
│ ChangeNotifierProvider │ ← 提供数据
│ (TaskProvider) │
├─────────────────────────────────────┤
│ TodoApp │
├─────────────────────────────────────┤
│ HomeScreen │
├─────────────────────────────────────┤
│ ┌─────────┐ ┌─────────────┐ │
│ │ AppBar │ │ Consumer │ │ ← 使用数据
│ │ (显示数量)│ │ (TaskList) │ │
│ └─────────┘ └─────────────┘ │
└─────────────────────────────────────┘安装 Provider
在 pubspec.yaml 中添加依赖:
1dependencies:
2 flutter:
3 sdk: flutter
4 provider: ^6.1.1 # 添加这一行然后运行:
1flutter pub get6.3 在应用中使用 Provider
1. 在 main.dart 中配置 Provider
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => TaskProvider(), // 创建 Provider
child: const TodoApp(), // 子树可以访问 Provider
),
);
}2. 在 Widget 中读取数据
// 方式 1:使用 Consumer(推荐)
Consumer(
builder: (context, provider, child) {
return Text('任务数: ${provider.totalTaskCount}');
},
)
// 方式 2:使用 context.watch()
final provider = context.watch();
return Text('任务数: ${provider.totalTaskCount}');
// 方式 3:使用 context.read()(只读一次,不监听变化)
final provider = context.read();
provider.addTask('新任务');3. 监听变化并重建
当调用 notifyListeners() 时,所有使用 Consumer 或 context.watch() 的地方都会自动重建。
6.4 TaskProvider 核心方法解析
状态定义
class TaskProvider extends ChangeNotifier {
// 私有状态
final List _tasks = [];
TaskFilter _currentFilter = TaskFilter.all;
bool _isLoading = false;
String? _error;Getter 方法
// 提供只读访问
List get tasks => List.unmodifiable(_tasks);
TaskFilter get currentFilter => _currentFilter;
bool get isLoading => _isLoading;
String? get error => _error;
// 计算属性
List get filteredTasks {
switch (_currentFilter) {
case TaskFilter.all:
return _tasks;
case TaskFilter.active:
return _tasks.where((task) => !task.isCompleted).toList();
case TaskFilter.completed:
return _tasks.where((task) => task.isCompleted).toList();
}
}
int get activeTaskCount =>
_tasks.where((task) => !task.isCompleted).length;添加任务
Future addTask(String title, {DateTime? dueDate}) async {
// 1. 验证输入
if (title.trim().isEmpty) {
_setError('任务标题不能为空');
return;
}
// 2. 创建新任务
final newTask = Task(
id: _uuid.v4(),
title: title.trim(),
createdAt: DateTime.now(),
dueDate: dueDate,
);
// 3. 添加到列表
_tasks.insert(0, newTask);
_clearError();
// 4. 通知 UI 更新
notifyListeners();
// 5. 保存到本地
await _persistTasks();
}切换任务状态
Future toggleTask(String taskId) async {
// 1. 找到任务索引
final index = _tasks.indexWhere((task) => task.id == taskId);
if (index == -1) return;
// 2. 使用 copyWith 创建修改后的副本
_tasks[index] = _tasks[index].copyWith(
isCompleted: !_tasks[index].isCompleted,
);
// 3. 通知 UI 更新
notifyListeners();
// 4. 保存到本地
await _persistTasks();
}删除任务
Future deleteTask(String taskId) async {
_tasks.removeWhere((task) => task.id == taskId);
notifyListeners();
await _persistTasks();
}6.5 在 UI 中使用 Provider
完整示例
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
// 使用 Consumer 监听任务数量变化
appBar: AppBar(
title: Consumer(
builder: (context, provider, child) {
return Text('待办事项 (${provider.totalTaskCount})');
},
),
),
// 使用 Consumer 显示任务列表
body: Consumer(
builder: (context, provider, child) {
// 显示加载状态
if (provider.isLoading) {
return CircularProgressIndicator();
}
// 显示错误
if (provider.error != null) {
return Text('错误: ${provider.error}');
}
// 显示任务列表
final tasks = provider.filteredTasks;
return ListView.builder(
itemCount: tasks.length,
itemBuilder: (context, index) {
final task = tasks[index];
return ListTile(
title: Text(task.title),
trailing: Checkbox(
value: task.isCompleted,
onChanged: (_) {
// 调用 Provider 的方法
provider.toggleTask(task.id);
},
),
);
},
);
},
),
// 添加任务按钮
floatingActionButton: FloatingActionButton(
onPressed: () {
// 使用 context.read() 获取 Provider 并调用方法
context.read().addTask('新任务');
},
child: Icon(Icons.add),
),
);
}
}6.6 Provider 的使用模式
模式 1:Consumer(推荐用于构建 UI)
Consumer(
builder: (context, provider, child) {
return Text('${provider.totalTaskCount} 个任务');
},
)适用场景: 需要根据数据变化更新 UI 的组件。
模式 2:context.watch()
final provider = context.watch();
return Text('${provider.totalTaskCount} 个任务');适用场景: 整个 Widget 都需要监听数据变化。
模式 3:context.read()
// 在事件回调中使用
onPressed: () {
context.read().addTask('新任务');
}适用场景: 只需要调用方法,不需要监听变化(如按钮点击)。
模式 4:Selector(性能优化)
Selector(
selector: (context, provider) => provider.totalTaskCount,
builder: (context, count, child) {
return Text('$count 个任务');
},
)适用场景: 只监听特定数据的变化,避免不必要的重建。
6.7 数据流向图
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 用户操作 │────▶│ Provider │────▶│ 更新状态 │
│ (点击按钮) │ │ (业务逻辑) │ │ (notify) │
└─────────────┘ └─────────────┘ └──────┬──────┘
│
▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 显示更新 │◀────│ Consumer │◀────│ 重建 UI │
│ (新数据) │ │ (监听变化) │ │ (build) │
└─────────────┘ └─────────────┘ └─────────────┘6.8 小结
| 概念 | 说明 |
|---|---|
| ChangeNotifier | 提供通知机制的基础类 |
| notifyListeners() | 通知所有监听者数据已改变 |
| Consumer | 监听数据变化并重建 UI |
| context.watch() | 监听数据变化 |
| context.read() | 只读取一次,不监听变化 |
| Selector | 选择性监听,优化性能 |
Provider 使用三步曲:
- 创建 - 继承 ChangeNotifier,定义状态和方法
- 提供 - 在顶层使用 ChangeNotifierProvider 包裹
- 消费 - 在子组件使用 Consumer 或 context.watch() 获取数据
下一步:第七章:UI 组件详解