【Flutter Todo 实战】第六章:状态管理详解

· 5 min read

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 get

6.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() 时,所有使用 Consumercontext.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 使用三步曲:

  1. 创建 - 继承 ChangeNotifier,定义状态和方法
  2. 提供 - 在顶层使用 ChangeNotifierProvider 包裹
  3. 消费 - 在子组件使用 Consumer 或 context.watch() 获取数据

下一步第七章:UI 组件详解

Related Articles