← 返回学习路线

状态管理

深入掌握 Flutter 状态管理的各种方案,从基础的 setState 到 Provider、Riverpod、BLoC 等主流框架,构建可维护的大型应用

📚 第一章:状态管理概述

1.1 什么是状态(State)

在 Flutter 中,状态是指任何在应用运行时可能发生变化的数据。UI 是状态的函数:UI = f(State)。当状态改变时,Flutter 会重新构建受影响的 Widget 树来反映最新的数据。

状态无处不在:用户输入的文本、购物车中的商品列表、是否已登录、当前选中的选项卡、从网络加载的数据等等,都属于状态。

1.2 临时状态 vs 应用状态

Flutter 团队将状态划分为两大类,理解这一区分对于选择合适的管理方案至关重要:

临时状态(Ephemeral State)

  • 又称 UI 状态或局部状态
  • 仅影响单个 Widget
  • 例:PageView 的当前页、动画进度、BottomNavigationBar 的选中项
  • 通常用 setState 管理即可
  • 不需要序列化,不需要跨 Widget 共享

应用状态(App State)

  • 又称共享状态或全局状态
  • 需要在多个 Widget 之间共享
  • 例:用户登录信息、购物车内容、通知设置、文章列表
  • 需要使用专业的状态管理方案
  • 可能需要持久化和序列化

1.3 为什么状态管理很重要

随着应用规模增长,如果没有良好的状态管理策略,代码会变得混乱不堪:

  • 数据一致性:多处展示同一数据时,如何确保它们同步更新
  • 代码可维护性:状态逻辑与 UI 逻辑分离,便于测试和重构
  • 性能优化:精确控制哪些 Widget 需要重建,避免不必要的渲染
  • 团队协作:统一的状态管理模式让团队成员更容易理解代码
  • 调试便利:可追踪的状态变化使 Bug 更容易定位

1.4 状态管理方案的演进

Flutter 社区已经发展出多种成熟的状态管理方案,从简单到复杂:

  1. setState — Flutter 内置,最基础的方式
  2. InheritedWidget — Flutter 内置的状态传递机制
  3. ValueNotifier / ChangeNotifier — Flutter 内置的观察者模式
  4. Provider — 官方推荐,对 InheritedWidget 的封装
  5. Riverpod — Provider 作者的全新力作,编译时安全
  6. BLoC — 基于流(Stream)的业务逻辑组件
  7. GetX — 轻量级全能框架
建议:没有"银弹"方案。小型项目用 setState + Provider 足够;中大型项目可考虑 Riverpod 或 BLoC;选择时应考虑团队熟悉度、项目规模和长期可维护性。

🔄 第二章:setState

2.1 工作原理

setStateStatefulWidget 中最基本的状态管理方式。调用 setState() 会通知 Flutter 框架:这个 State 对象的内部状态发生了变化,需要重新调用 build() 方法来更新 UI。

其内部流程如下:

  1. 调用 setState(VoidCallback fn)
  2. 执行回调函数 fn,修改状态变量
  3. 将当前 Element 标记为"dirty"(需要重建)
  4. 在下一帧,Flutter 调用 build() 重新构建该 Widget 子树
dart
// setState 的源码简化版
void setState(VoidCallback fn) {
  // 执行回调修改状态
  fn();
  // 将 Element 标记为 dirty,等待下一帧重建
  _element!.markNeedsBuild();
}

2.2 基本使用

dart
class CounterPage extends StatefulWidget {
  const CounterPage({super.key});

  @override
  State<CounterPage> createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  int _count = 0;

  void _increment() {
    setState(() {
      _count++;
    });
  }

  void _decrement() {
    setState(() {
      if (_count > 0) _count--;
    });
  }

  void _reset() {
    setState(() {
      _count = 0;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('计数器: $_count')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              '$_count',
              style: TextStyle(fontSize: 80, fontWeight: FontWeight.bold),
            ),
            SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(onPressed: _decrement, child: Icon(Icons.remove)),
                SizedBox(width: 16),
                ElevatedButton(onPressed: _reset, child: Icon(Icons.refresh)),
                SizedBox(width: 16),
                ElevatedButton(onPressed: _increment, child: Icon(Icons.add)),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

2.3 最佳实践

  • 在回调内修改状态:始终在 setState 的回调函数内修改变量,保持代码清晰
  • 最小化 setState 影响范围:将频繁变化的部分提取为独立的 StatefulWidget
  • 避免在 build 中调用 setState:这会导致无限循环
  • 检查 mounted 属性:异步操作完成后调用 setState 前,先检查 Widget 是否还在树中
dart
// 异步操作中安全使用 setState
Future<void> _loadData() async {
  final data = await fetchFromApi();
  // 检查 Widget 是否还挂载在树上
  if (!mounted) return;
  setState(() {
    _data = data;
    _isLoading = false;
  });
}

2.4 反模式(Anti-patterns)

警告:以下是常见的 setState 错误用法,务必避免。
dart
// 反模式 1: 在回调外修改状态
_count++;             // 先修改了
setState(() {});      // 空回调 - 虽然能工作但不推荐

// 反模式 2: 在 build 中调用 setState
@override
Widget build(BuildContext context) {
  setState(() { _x = 1; }); // 无限循环!
  return Container();
}

// 反模式 3: 在整个页面级别使用 setState 更新小组件
// 会导致整个页面重建,性能浪费

// 反模式 4: 异步后不检查 mounted
Future<void> _fetch() async {
  final data = await api.get();
  setState(() { _data = data; }); // Widget 可能已被销毁!
}
适用场景:setState 适合管理单个 Widget 内部的临时状态,如动画控制、表单输入、展开/收起等。当状态需要跨 Widget 共享时,应考虑其他方案。

🔗 第三章:InheritedWidget

3.1 工作原理

InheritedWidget 是 Flutter 内置的状态传递机制,它允许数据沿 Widget 树向下传递,而不需要逐层通过构造函数传参。Theme.of(context)MediaQuery.of(context) 等底层 API 都基于这个机制。

当 InheritedWidget 的数据发生变化时,所有通过 dependOnInheritedWidgetOfExactType 订阅了它的后代 Widget 都会被通知重建。

3.2 自定义 InheritedWidget

dart
// 1. 定义共享数据和 InheritedWidget
class UserData {
  final String name;
  final String email;
  final String avatarUrl;

  const UserData({
    required this.name,
    required this.email,
    required this.avatarUrl,
  });
}

class UserDataWidget extends InheritedWidget {
  final UserData userData;

  const UserDataWidget({
    super.key,
    required this.userData,
    required super.child,
  });

  // 提供便捷的 of(context) 方法
  static UserData of(BuildContext context) {
    final widget = context
        .dependOnInheritedWidgetOfExactType<UserDataWidget>();
    assert(widget != null, 'No UserDataWidget found in context');
    return widget!.userData;
  }

  // 可选:不订阅更新的读取方式
  static UserData? maybeOf(BuildContext context) {
    return context
        .dependOnInheritedWidgetOfExactType<UserDataWidget>()
        ?.userData;
  }

  @override
  bool updateShouldNotify(UserDataWidget oldWidget) {
    // 只有在数据确实改变时才通知后代
    return userData.name != oldWidget.userData.name ||
           userData.email != oldWidget.userData.email;
  }
}

3.3 使用 InheritedWidget

dart
// 2. 在顶层提供数据
class MyApp extends StatefulWidget {
  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  UserData _user = UserData(
    name: '张三',
    email: 'zhang@example.com',
    avatarUrl: 'https://example.com/avatar.png',
  );

  @override
  Widget build(BuildContext context) {
    return UserDataWidget(
      userData: _user,
      child: MaterialApp(home: HomePage()),
    );
  }
}

// 3. 在任意后代 Widget 中读取数据
class UserProfile extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 通过 of(context) 获取数据
    final user = UserDataWidget.of(context);

    return Column(
      children: [
        CircleAvatar(backgroundImage: NetworkImage(user.avatarUrl)),
        Text(user.name, style: TextStyle(fontSize: 20)),
        Text(user.email),
      ],
    );
  }
}

3.4 of(context) 模式详解

of(context) 是 Flutter 中非常重要的设计模式,几乎所有内置服务都采用这种方式:

  • Theme.of(context) — 获取主题数据
  • MediaQuery.of(context) — 获取屏幕尺寸信息
  • Navigator.of(context) — 获取导航器
  • Scaffold.of(context) — 获取 Scaffold 状态

调用 dependOnInheritedWidgetOfExactType 会让当前 Widget 订阅该 InheritedWidget 的更新。如果只是读取而不需要订阅,可以使用 getInheritedWidgetOfExactType 代替。

注意:InheritedWidget 本身是不可变的(immutable),要实现数据更新,通常需要将它与 StatefulWidget 结合使用,在外层的 State 中通过 setState 来替换 InheritedWidget 实例。
提示:直接使用 InheritedWidget 代码较多且模板化。在实际项目中,推荐使用 Provider,它对 InheritedWidget 进行了优雅的封装,大大简化了使用方式。

🔔 第四章:ValueNotifier 与 ChangeNotifier

4.1 ValueNotifier

ValueNotifier 是一个持有单个值并在值改变时通知监听器的简单类。它是 ChangeNotifier 的子类,非常适合管理简单的单一状态值。

dart
// ValueNotifier 持有单个值
final counter = ValueNotifier<int>(0);

// 修改值 - 自动通知监听器
counter.value++;
counter.value = 42;

// 手动添加监听器
counter.addListener(() {
  print('新值: ${counter.value}');
});

// 记得在不需要时移除监听器
counter.removeListener(myListener);
counter.dispose();

4.2 ValueListenableBuilder

ValueListenableBuilder 是配合 ValueNotifier 使用的 Widget,它只重建自身包裹的部分,性能优秀。

dart
class CounterPage extends StatefulWidget {
  @override
  State<CounterPage> createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  // 定义在 State 中,随 State 的生命周期管理
  final ValueNotifier<int> _counter = ValueNotifier<int>(0);

  @override
  void dispose() {
    _counter.dispose();  // 务必释放资源
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('ValueNotifier 示例')),
      body: Center(
        child: ValueListenableBuilder<int>(
          valueListenable: _counter,
          // 只有这个 builder 会在值变化时重建
          builder: (context, value, child) {
            return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                child!,  // 不变的部分,不会重建
                Text('$value', style: TextStyle(fontSize: 60)),
              ],
            );
          },
          child: Text('当前计数'),  // 缓存的子 Widget
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _counter.value++,
        child: Icon(Icons.add),
      ),
    );
  }
}

4.3 ChangeNotifier

ChangeNotifier 是一个可以被监听的类,适合管理更复杂的多字段状态。它是 Provider 和很多状态管理方案的基础。

dart
// 定义一个完整的状态模型
class ShoppingCart extends ChangeNotifier {
  final List<CartItem> _items = [];

  // 对外暴露不可修改的列表
  List<CartItem> get items => List.unmodifiable(_items);
  int get itemCount => _items.length;
  double get totalPrice =>
      _items.fold(0, (sum, item) => sum + item.price * item.quantity);

  void addItem(CartItem item) {
    final index = _items.indexWhere((e) => e.id == item.id);
    if (index >= 0) {
      _items[index] = _items[index].copyWith(
        quantity: _items[index].quantity + 1,
      );
    } else {
      _items.add(item);
    }
    notifyListeners();  // 通知所有监听器
  }

  void removeItem(String id) {
    _items.removeWhere((e) => e.id == id);
    notifyListeners();
  }

  void clear() {
    _items.clear();
    notifyListeners();
  }
}
ValueNotifier vs ChangeNotifier:ValueNotifier 适合管理单个简单值(int、String、bool 等),ChangeNotifier 适合管理包含多个字段和复杂逻辑的状态对象。两者都是 Provider 的基础。

📦 第五章:Provider

5.1 概述

Provider 是 Flutter 官方推荐的状态管理方案,由 Remi Rousselet 开发。它是对 InheritedWidget 的高级封装,提供了简洁、高效、可测试的状态管理能力。

yaml
# pubspec.yaml
dependencies:
  provider: ^6.1.1

5.2 ChangeNotifierProvider

最常用的 Provider 类型,用于提供一个 ChangeNotifier 实例,并在其调用 notifyListeners() 时自动通知消费者重建。

dart
// 在 Widget 树顶层提供状态
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => TodoModel(),
      child: MyApp(),
    ),
  );
}

// 在任意后代中读取状态
// 方式1: context.watch - 监听变化并重建
final todos = context.watch<TodoModel>();

// 方式2: context.read - 只读取一次,不监听(适合在事件处理中使用)
context.read<TodoModel>().addTodo('新任务');

// 方式3: context.select - 只监听特定字段
final count = context.select<TodoModel, int>(
  (model) => model.completedCount,
);

5.3 Consumer 和 Selector

ConsumerSelector 是 Widget 形式的监听方式,可以精确控制重建范围,避免不必要的 rebuild。

dart
// Consumer - 监听整个 Model 的变化
Consumer<TodoModel>(
  builder: (context, todoModel, child) {
    return Text('任务数: ${todoModel.todos.length}');
  },
)

// Selector - 只在特定值变化时重建,性能更优
Selector<TodoModel, int>(
  selector: (_, model) => model.completedCount,
  builder: (context, completedCount, child) {
    return Text('已完成: $completedCount');
  },
)

5.4 MultiProvider

当需要提供多个状态时,使用 MultiProvider 避免嵌套。

dart
runApp(
  MultiProvider(
    providers: [
      ChangeNotifierProvider(create: (_) => AuthModel()),
      ChangeNotifierProvider(create: (_) => TodoModel()),
      ChangeNotifierProvider(create: (_) => ThemeModel()),
      // ProxyProvider 可以依赖其他 Provider
      ProxyProvider<AuthModel, ApiService>(
        update: (_, auth, __) => ApiService(auth.token),
      ),
    ],
    child: MyApp(),
  ),
);

5.5 完整 TODO 应用示例

dart
// ===== 数据模型 =====
class Todo {
  final String id;
  final String title;
  final bool completed;

  const Todo({
    required this.id,
    required this.title,
    this.completed = false,
  });

  Todo copyWith({String? title, bool? completed}) {
    return Todo(
      id: id,
      title: title ?? this.title,
      completed: completed ?? this.completed,
    );
  }
}

// ===== 状态管理模型 =====
class TodoModel extends ChangeNotifier {
  final List<Todo> _todos = [];

  List<Todo> get todos => List.unmodifiable(_todos);
  int get totalCount => _todos.length;
  int get completedCount => _todos.where((t) => t.completed).length;
  int get pendingCount => totalCount - completedCount;

  void add(String title) {
    _todos.add(Todo(
      id: DateTime.now().millisecondsSinceEpoch.toString(),
      title: title,
    ));
    notifyListeners();
  }

  void toggle(String id) {
    final index = _todos.indexWhere((t) => t.id == id);
    if (index != -1) {
      _todos[index] = _todos[index].copyWith(
        completed: !_todos[index].completed,
      );
      notifyListeners();
    }
  }

  void remove(String id) {
    _todos.removeWhere((t) => t.id == id);
    notifyListeners();
  }
}

// ===== 主应用 =====
class TodoApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('待办事项'),
        actions: [
          // 只在 completedCount 变化时重建
          Selector<TodoModel, int>(
            selector: (_, m) => m.completedCount,
            builder: (_, count, __) => Center(
              child: Padding(
                padding: EdgeInsets.all(16),
                child: Text('完成: $count'),
              ),
            ),
          ),
        ],
      ),
      body: Consumer<TodoModel>(
        builder: (context, model, _) {
          if (model.todos.isEmpty) {
            return Center(child: Text('暂无待办事项'));
          }
          return ListView.builder(
            itemCount: model.todos.length,
            itemBuilder: (_, index) {
              final todo = model.todos[index];
              return ListTile(
                leading: Checkbox(
                  value: todo.completed,
                  onChanged: (_) => model.toggle(todo.id),
                ),
                title: Text(
                  todo.title,
                  style: TextStyle(
                    decoration: todo.completed
                        ? TextDecoration.lineThrough
                        : null,
                  ),
                ),
                trailing: IconButton(
                  icon: Icon(Icons.delete_outline),
                  onPressed: () => model.remove(todo.id),
                ),
              );
            },
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _showAddDialog(context),
        child: Icon(Icons.add),
      ),
    );
  }

  void _showAddDialog(BuildContext context) {
    final controller = TextEditingController();
    showDialog(
      context: context,
      builder: (_) => AlertDialog(
        title: Text('添加待办'),
        content: TextField(
          controller: controller,
          autofocus: true,
          decoration: InputDecoration(hintText: '输入任务内容'),
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: Text('取消'),
          ),
          ElevatedButton(
            onPressed: () {
              if (controller.text.isNotEmpty) {
                context.read<TodoModel>().add(controller.text);
                Navigator.pop(context);
              }
            },
            child: Text('添加'),
          ),
        ],
      ),
    );
  }
}
性能技巧:在事件回调(onPressed、onTap)中使用 context.read 而非 context.watch,因为回调中不需要监听变化。context.watch 只应在 build 方法中使用。

🚀 第六章:Riverpod

6.1 概述

Riverpod 是 Provider 作者 Remi Rousselet 开发的新一代状态管理方案。它解决了 Provider 的诸多限制:不依赖 BuildContext、编译时安全、支持多个同类型 Provider、更好的异步支持。

yaml
# pubspec.yaml
dependencies:
  flutter_riverpod: ^2.4.9
  riverpod_annotation: ^2.3.3
dev_dependencies:
  riverpod_generator: ^2.3.9
  build_runner: ^2.4.8

6.2 Provider 类型

Provider

暴露一个只读值,不可修改。适合计算/派生值。

StateProvider

暴露一个可修改的简单值(int、String 等)。

StateNotifierProvider

暴露一个 StateNotifier,适合复杂的状态逻辑。

FutureProvider

暴露一个 Future 的结果,自动处理加载和错误状态。

StreamProvider

暴露一个 Stream 的结果,适合实时数据。

NotifierProvider

Riverpod 2.0 推荐,替代 StateNotifierProvider。

6.3 基础用法

dart
import 'package:flutter_riverpod/flutter_riverpod.dart';

// 1. 用 ProviderScope 包裹应用
void main() {
  runApp(
    ProviderScope(child: MyApp()),
  );
}

// 2. 定义全局 Provider(顶层变量,编译时安全)
final counterProvider = StateProvider<int>((ref) => 0);

// 3. 在 Widget 中消费(继承 ConsumerWidget)
class CounterPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // ref.watch: 监听变化并重建
    final count = ref.watch(counterProvider);

    return Scaffold(
      body: Center(child: Text('$count', style: TextStyle(fontSize: 60))),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // ref.read: 只读取,不监听(用于事件回调)
          ref.read(counterProvider.notifier).state++;
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

6.4 ref.watch / ref.read / ref.listen

dart
class MyPage extends ConsumerStatefulWidget {
  @override
  ConsumerState<MyPage> createState() => _MyPageState();
}

class _MyPageState extends ConsumerState<MyPage> {
  @override
  void initState() {
    super.initState();
    // ref.listen: 监听变化执行副作用(显示 SnackBar 等)
    ref.listenManual(counterProvider, (prev, next) {
      if (next == 10) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('已到达 10!')),
        );
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    // ref.watch: 在 build 中使用,自动重建
    final count = ref.watch(counterProvider);

    return Text('$count');
  }
}

6.5 AsyncValue 处理异步状态

dart
// FutureProvider 自动处理异步
final userProvider = FutureProvider<User>((ref) async {
  final response = await http.get(Uri.parse('https://api.example.com/user'));
  return User.fromJson(jsonDecode(response.body));
});

// 在 Widget 中使用 AsyncValue
class UserPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final userAsync = ref.watch(userProvider);

    // AsyncValue 的模式匹配 - 优雅处理三种状态
    return userAsync.when(
      loading: () => CircularProgressIndicator(),
      error: (error, stack) => Text('错误: $error'),
      data: (user) => Text('你好, ${user.name}'),
    );
  }
}

6.6 Family 和 autoDispose

dart
// Family: 带参数的 Provider
final todoProvider = FutureProvider.family<Todo, int>((ref, todoId) async {
  final response = await http.get(
    Uri.parse('https://api.example.com/todos/$todoId'),
  );
  return Todo.fromJson(jsonDecode(response.body));
});

// 使用时传入参数
final todo = ref.watch(todoProvider(42));

// autoDispose: 不再被监听时自动释放
final searchProvider = FutureProvider.autoDispose
    .family<List<Product>, String>((ref, query) async {
  // 防抖:取消之前的请求
  ref.onDispose(() {
    // 清理资源
  });
  // 保持状态一段时间,避免反复创建
  ref.keepAlive();

  return searchProducts(query);
});

6.7 Notifier(Riverpod 2.0 推荐)

dart
// 使用 Notifier 管理复杂状态
class TodoList extends Notifier<List<Todo>> {
  @override
  List<Todo> build() => [];  // 初始状态

  void add(String title) {
    state = [
      ...state,
      Todo(id: uuid(), title: title),
    ];
  }

  void toggle(String id) {
    state = [
      for (final todo in state)
        if (todo.id == id)
          todo.copyWith(completed: !todo.completed)
        else
          todo,
    ];
  }

  void remove(String id) {
    state = state.where((t) => t.id != id).toList();
  }
}

final todoListProvider = NotifierProvider<TodoList, List<Todo>>(
  TodoList.new,
);

// AsyncNotifier 处理异步
class AsyncTodoList extends AsyncNotifier<List<Todo>> {
  @override
  Future<List<Todo>> build() async {
    return fetchTodos();
  }

  Future<void> add(String title) async {
    state = const AsyncValue.loading();
    state = await AsyncValue.guard(() async {
      await createTodo(title);
      return fetchTodos();
    });
  }
}
Riverpod vs Provider:Riverpod 不依赖 Widget 树,Provider 是全局声明的,编译时类型安全,支持多个同类型 Provider,更好的测试支持(可以 override 任意 Provider)。新项目推荐使用 Riverpod。

🎯 第七章:BLoC / Cubit

7.1 概述

BLoC(Business Logic Component)是一种基于事件驱动和流(Stream)的架构模式。Cubit 是 BLoC 的简化版本,不需要定义事件类,直接通过方法调用来改变状态。

yaml
# pubspec.yaml
dependencies:
  flutter_bloc: ^8.1.3
  bloc: ^8.1.2
  equatable: ^2.0.5

7.2 Cubit(推荐入门)

Cubit 比 BLoC 更简洁,适合大部分场景。

dart
// 定义状态
class CounterState extends Equatable {
  final int count;
  final bool isLoading;

  const CounterState({this.count = 0, this.isLoading = false});

  CounterState copyWith({int? count, bool? isLoading}) {
    return CounterState(
      count: count ?? this.count,
      isLoading: isLoading ?? this.isLoading,
    );
  }

  @override
  List<Object> get props => [count, isLoading];
}

// 定义 Cubit
class CounterCubit extends Cubit<CounterState> {
  CounterCubit() : super(const CounterState());

  void increment() => emit(state.copyWith(count: state.count + 1));
  void decrement() => emit(state.copyWith(count: state.count - 1));
  void reset() => emit(const CounterState());

  Future<void> incrementAsync() async {
    emit(state.copyWith(isLoading: true));
    await Future.delayed(Duration(seconds: 1));
    emit(state.copyWith(count: state.count + 1, isLoading: false));
  }
}

7.3 BlocProvider 和 BlocBuilder

dart
// 提供 Cubit/BLoC
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => CounterCubit(),
      child: MaterialApp(home: CounterPage()),
    );
  }
}

// BlocBuilder: 根据状态重建 UI
class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('BLoC 计数器')),
      body: Center(
        child: BlocBuilder<CounterCubit, CounterState>(
          // buildWhen: 精确控制何时重建
          buildWhen: (prev, curr) => prev.count != curr.count,
          builder: (context, state) {
            if (state.isLoading) {
              return CircularProgressIndicator();
            }
            return Text(
              '${state.count}',
              style: TextStyle(fontSize: 60),
            );
          },
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            heroTag: 'increment',
            onPressed: () => context.read<CounterCubit>().increment(),
            child: Icon(Icons.add),
          ),
          SizedBox(height: 8),
          FloatingActionButton(
            heroTag: 'decrement',
            onPressed: () => context.read<CounterCubit>().decrement(),
            child: Icon(Icons.remove),
          ),
        ],
      ),
    );
  }
}

7.4 BlocConsumer

BlocConsumer 结合了 BlocBuilder(构建 UI)和 BlocListener(监听副作用),适合需要同时响应状态变化并执行副作用的场景。

dart
BlocConsumer<CounterCubit, CounterState>(
  // listener: 执行副作用(导航、弹窗、SnackBar 等)
  listenWhen: (prev, curr) => prev.count != curr.count,
  listener: (context, state) {
    if (state.count == 10) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('恭喜,到达 10!')),
      );
    }
  },
  // builder: 构建 UI
  buildWhen: (prev, curr) => prev.count != curr.count,
  builder: (context, state) {
    return Text('${state.count}', style: TextStyle(fontSize: 48));
  },
)

7.5 完整 BLoC 示例(事件驱动)

dart
// ===== 事件定义 =====
sealed class AuthEvent {}
class LoginRequested extends AuthEvent {
  final String email;
  final String password;
  LoginRequested({required this.email, required this.password});
}
class LogoutRequested extends AuthEvent {}

// ===== 状态定义 =====
sealed class AuthState {}
class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {}
class AuthSuccess extends AuthState {
  final User user;
  AuthSuccess(this.user);
}
class AuthFailure extends AuthState {
  final String message;
  AuthFailure(this.message);
}

// ===== BLoC 定义 =====
class AuthBloc extends Bloc<AuthEvent, AuthState> {
  final AuthRepository _repo;

  AuthBloc(this._repo) : super(AuthInitial()) {
    on<LoginRequested>(_onLogin);
    on<LogoutRequested>(_onLogout);
  }

  Future<void> _onLogin(
    LoginRequested event,
    Emitter<AuthState> emit,
  ) async {
    emit(AuthLoading());
    try {
      final user = await _repo.login(event.email, event.password);
      emit(AuthSuccess(user));
    } catch (e) {
      emit(AuthFailure(e.toString()));
    }
  }

  Future<void> _onLogout(
    LogoutRequested event,
    Emitter<AuthState> emit,
  ) async {
    await _repo.logout();
    emit(AuthInitial());
  }
}

// ===== 使用 =====
BlocProvider(
  create: (context) => AuthBloc(AuthRepository()),
  child: BlocConsumer<AuthBloc, AuthState>(
    listener: (context, state) {
      if (state is AuthFailure) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text(state.message)),
        );
      }
    },
    builder: (context, state) {
      return switch (state) {
        AuthInitial() => LoginForm(),
        AuthLoading() => CircularProgressIndicator(),
        AuthSuccess(user: final u) => HomePage(user: u),
        AuthFailure() => LoginForm(),
      };
    },
  ),
)
Cubit vs BLoC:Cubit 更简洁,适合简单逻辑;BLoC 有事件驱动的优势,便于追踪和重放,适合复杂的业务逻辑和需要事件溯源的场景。

⚡ 第八章:GetX 简介

8.1 概述

GetX 是一个轻量但功能强大的 Flutter 解决方案,集成了状态管理、路由管理和依赖注入三大功能。使用简单,但在大型项目中的可维护性存在争议。

yaml
# pubspec.yaml
dependencies:
  get: ^4.6.6

8.2 响应式状态管理(Obs + Obx)

dart
import 'package:get/get.dart';

// 定义控制器
class CounterController extends GetxController {
  // .obs 让变量变成响应式
  var count = 0.obs;
  var name = 'Flutter'.obs;
  var items = <String>[].obs;

  void increment() => count++;
  void changeName(String n) => name.value = n;
  void addItem(String item) => items.add(item);
}

// 使用
class CounterPage extends StatelessWidget {
  final controller = Get.put(CounterController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Obx(() => Text(
          '${controller.count}',
          style: TextStyle(fontSize: 48),
        )),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: controller.increment,
        child: Icon(Icons.add),
      ),
    );
  }
}

8.3 简单状态管理(GetBuilder)

dart
// 不使用响应式,手动调用 update()
class SimpleController extends GetxController {
  int count = 0;

  void increment() {
    count++;
    update();  // 手动通知更新,类似 setState
  }
}

// GetBuilder 性能更好,不使用 Stream
GetBuilder<SimpleController>(
  init: SimpleController(),
  builder: (ctrl) => Text('${ctrl.count}'),
)

8.4 优缺点分析

优点

  • 语法极其简洁,学习成本低
  • 集成状态管理 + 路由 + 依赖注入
  • 不需要 BuildContext
  • 响应式更新,性能不错
  • 快速开发小型项目

缺点

  • "魔法"太多,隐式行为不透明
  • 与 Flutter 设计哲学不一致
  • 大型项目中可维护性存疑
  • 依赖注入全局化,难以测试
  • 社区有较大争议
注意:GetX 在快速原型开发和小型项目中很方便,但在正式的大型项目中,建议优先考虑 Provider、Riverpod 或 BLoC 等更符合 Flutter 设计理念、可测试性更强的方案。

📊 第九章:方案对比

9.1 综合对比表

方案 学习曲线 样板代码 可测试性 适合规模 依赖 Context
setState 极低 极少 单组件
InheritedWidget 中等 中等 小型
ValueNotifier 中等 小型
Provider 中大型
Riverpod 中等 极高 中大型
BLoC 较高 极高 大型
GetX 极低 极少 小型

9.2 选择建议

个人项目 / 学习

setState + Provider 足够。先掌握基础,再探索其他方案。

中小型团队项目

推荐 Riverpod 或 Provider。Riverpod 类型更安全,Provider 生态更成熟。

大型企业项目

推荐 BLoC 或 Riverpod。BLoC 的事件驱动模型适合复杂业务流程,可追溯性强。

快速原型 / MVP

GetX 或 Provider。GetX 开发速度最快,但不推荐长期维护的项目使用。

9.3 关键决策因素

  • 团队经验:选择团队最熟悉的方案,降低上手成本
  • 项目规模:小项目不要过度工程化,大项目需要严谨的架构
  • 测试需求:如果需要高测试覆盖率,选择 Riverpod 或 BLoC
  • 异步处理:如果有大量异步操作,Riverpod 的 AsyncValue 体验最佳
  • 可维护性:长期项目优先考虑代码可读性和社区支持度
核心原则:状态管理方案只是工具,最重要的是将业务逻辑与 UI 分离,保持单一职责,让代码易于测试和维护。无论使用哪种方案,都要遵循这一原则。

💻 第十章:实践练习

练习 1:主题切换器(Provider)

使用 Provider 实现一个支持浅色/深色主题切换的应用:

  1. 创建 ThemeModel extends ChangeNotifier,包含 themeMode 属性
  2. 使用 ChangeNotifierProvider 在顶层提供 ThemeModel
  3. 在 MaterialApp 中根据 ThemeModel 设置 themeMode
  4. 创建设置页面,使用 Consumercontext.watch 显示当前主题
  5. 添加 Switch 组件切换主题,使用 context.read 调用切换方法
  6. 扩展:支持三种模式(浅色、深色、跟随系统),使用 RadioListTile 选择

练习 2:购物车应用(Riverpod)

使用 Riverpod 构建一个完整的购物车功能:

  1. 定义 Product 数据模型(id、name、price、imageUrl)
  2. 创建 productsProvider(FutureProvider),模拟从 API 加载商品列表
  3. 创建 CartNotifier extends Notifier<List<CartItem>>,实现增/删/改数量
  4. 创建派生 Provider:cartTotalProvider 计算总价、cartCountProvider 计算商品数
  5. 商品列表页:使用 AsyncValue.when 处理加载/错误/成功状态
  6. 购物车页:展示已添加商品,支持修改数量和删除
  7. 在 AppBar 上用 Badge 显示购物车商品数量

练习 3:登录流程(BLoC)

使用 BLoC 模式实现完整的登录/注册流程:

  1. 定义事件:LoginSubmittedRegisterSubmittedLogoutRequested
  2. 定义状态:AuthInitialAuthLoadingAuthAuthenticatedAuthError
  3. 实现 AuthBloc,处理各种事件,包含表单验证逻辑
  4. 登录页面使用 BlocConsumer:listener 处理错误提示和导航,builder 构建表单
  5. 主页使用 BlocBuilder 根据认证状态显示不同内容
  6. 添加 BlocObserver 在控制台打印所有状态变化,便于调试
  7. 编写单元测试:使用 blocTest 验证事件到状态的转换逻辑