Skip to content

状态管理

状态管理是 Flutter 开发的核心问题。本文从最基础的 setState 开始,逐步讲解 InheritedWidgetProviderRiverpod,帮你在不同场景下选择合适的方案。

什么是状态管理?

想象一个场景:你在 App 顶部有一个购物车图标,显示商品数量;在商品列表页点击"加入购物车"后,顶部的数字要立刻更新。

问题:这两个组件不在同一个 Widget 树层级,怎么让它们共享同一份数据?

答案:这就是状态管理要解决的事。从简到繁,Flutter 有多种方案。

方案总览与选择

方案难度适用场景
setState组件内部简单状态
InheritedWidget⭐⭐自定义状态共享(理解原理)
Provider⭐⭐中小型项目状态共享
Riverpod⭐⭐⭐Provider 的改进版,更安全更灵活
Bloc / Cubit⭐⭐⭐大型项目、复杂业务逻辑
GetX快速开发(有维护风险)

选择指南:

项目规模 / 复杂度

├── 小型 Demo / 学习阶段
│   └── setState

├── 中型项目(几个页面共享状态)
│   └── Provider

├── 大型项目(复杂状态逻辑)
│   └── Riverpod 或 Bloc

└── 追求开发速度
    └── GetX(但要注意维护风险)

推荐学习路线: setStateProviderRiverpod


一、setState — 最基础的状态更新

setState() 是 Flutter 最基础的状态更新方式,用于在 StatefulWidgetState 类中通知框架状态已变更。

基本用法

dart
import 'package:flutter/material.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 ──────────────
    setState(() {
      _count++;   // 在回调中修改状态
    });
    // ─── ☆ setState ──────────────
    // setState 调用后,build() 会被重新执行
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('setState 示例')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('$_count', style: const TextStyle(fontSize: 48)),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: _increment,
              child: const Text('+1'),
            ),
          ],
        ),
      ),
    );
  }
}

工作原理

用户操作 → 调用 setState() → 框架标记dirty → 框架调用 build() → UI 更新

注意事项

不要在 setState 中执行耗时操作:

dart
import 'package:flutter/material.dart';

// ❌ 错误
void _loadData() {
  setState(() {
    final data = http.get(Uri.parse(url));  // 耗时操作!
    _items = data;
  });
}

// ✅ 正确
void _loadData() async {
  setState(() => _isLoading = true);   // 先显示 loading
  final data = await http.get(Uri.parse(url));
  if (!mounted) return;
  setState(() {
    _items = data;
    _isLoading = false;
  });
}

不要在 build 中调用 setState:

dart
// ❌ 错误:会导致无限循环
@override
Widget build(BuildContext context) {
  setState(() { _count++; });  // 在 build 中调用 setState → 无限循环!
  return Text('$_count');
}

异步操作后检查 mounted:

dart
import 'package:flutter/material.dart';

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

  @override
  State<DataPage> createState() => _DataPageState();
}

class _DataPageState extends State<DataPage> {
  String _data = '';

  void _fetchData() async {
    final result = await Future.delayed(const Duration(seconds: 2));
    if (!mounted) return;   // 组件已被销毁
    setState(() => _data = result.toString());
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('mounted 检查')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(_data.isEmpty ? '暂无数据' : _data),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: _fetchData,
              child: const Text('获取数据'),
            ),
          ],
        ),
      ),
    );
  }
}

setState 的局限性

局限说明
仅限当前 State无法在兄弟/跨层级组件间共享状态
每次全量重建build() 整体重建,大型 Widget 树性能较差
状态传递麻烦深层传递需要层层回调(callback hell)

二、InheritedWidget — 跨层级数据共享

InheritedWidget 是 Flutter 实现数据从上向下共享的基础机制。Provider 等状态管理方案都是基于它封装的。

手动实现示例

dart
// 1. 创建 InheritedWidget
class CounterData extends InheritedWidget {
  const CounterData({
    super.key,
    required this.count,
    required this.onIncrement,
    required super.child,
  });

  final int count;
  final VoidCallback onIncrement;

  // 静态方法:让子组件方便获取数据
  static CounterData of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<CounterData>()!;
  }

  // 决定是否通知依赖的子组件重建
  @override
  bool updateShouldNotify(CounterData oldWidget) {
    return count != oldWidget.count;
  }
}

// 2. 在 Widget 树中提供数据
class CounterProvider extends StatefulWidget {
  final Widget child;
  const CounterProvider({super.key, required this.child});

  @override
  State<CounterProvider> createState() => _CounterProviderState();
}

class _CounterProviderState extends State<CounterProvider> {
  int _count = 0;

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

  @override
  Widget build(BuildContext context) {
    return CounterData(
      count: _count,
      onIncrement: _increment,
      child: widget.child,
    );
  }
}

// 3. 在子组件中使用
class CounterDisplay extends StatelessWidget {
  const CounterDisplay({super.key});

  @override
  Widget build(BuildContext context) {
    final data = CounterData.of(context);  // 获取数据
    return Text('计数: ${data.count}');
  }
}

核心方法

  • dependOnInheritedWidgetOfExactType:注册依赖,数据变化时会重建该组件
  • getInheritedWidgetOfExactType:不注册依赖,数据变化时不会重建(只读取一次)

实际使用建议

手写 InheritedWidget 比较繁琐。在实际项目中,推荐直接使用 Provider,它是对 InheritedWidget 的优雅封装。理解 InheritedWidget 的原理有助于理解 Provider 的工作方式。


三、Provider — 官方推荐的状态管理

安装

bash
flutter pub add provider

核心思路(3 步)

1️⃣ 创建数据模型 → 2️⃣ 在顶层注入 → 3️⃣ 在子组件中读取

第一步:创建数据模型

dart
class CounterModel extends ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners(); // 告诉所有监听者:数据变了
  }

  void decrement() {
    _count--;
    notifyListeners();
  }
}

⚠️ 忘记写 notifyListeners() 是新手最常见的错误——数据改了但 UI 不更新。

第二步:在顶层注入

dart
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => CounterModel(),
      child: const MyApp(),
    ),
  );
}

第三步:在子组件中读取

dart
// watch — 显示数据(数据变了会自动重建)
class CounterText extends StatelessWidget {
  const CounterText({super.key});

  @override
  Widget build(BuildContext context) {
    final counter = context.watch<CounterModel>();
    return Text('当前计数:${counter.count}', style: const TextStyle(fontSize: 24));
  }
}

// read — 调用方法(不监听变化,不会触发重建)
class IncrementButton extends StatelessWidget {
  const IncrementButton({super.key});

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () => context.read<CounterModel>().increment(),
      child: const Text('+1'),
    );
  }
}

watch vs read(最重要)

watchread
用途读取数据并监听变化调用方法 / 一次性读取
数据变了会重建?✅ 会❌ 不会
典型场景显示文本、列表等按钮点击回调

记住这条规则:要显示数据 → watch,要调用方法 → read

Consumer — 精确控制重建范围

dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

// ... 假设 CounterModel 和 ChangeNotifierProvider 已定义

class CounterConsumerPage extends StatelessWidget {
  const CounterConsumerPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Consumer 示例')),
      body: Center(
        child: Consumer<CounterModel>(
          builder: (context, counter, child) {
            // 只有这里会重建,其他部分不会
            return Text(
              '计数:${counter.count}',
              style: const TextStyle(fontSize: 48),
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.read<CounterModel>().increment(),
        child: const Icon(Icons.add),
      ),
    );
  }
}

MultiProvider — 注入多个数据模型

dart
void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => UserModel()),
        ChangeNotifierProvider(create: (_) => CartModel()),
        ChangeNotifierProvider(create: (_) => ThemeModel()),
      ],
      child: const MyApp(),
    ),
  );
}

Provider 类型速查

类型适用场景
ChangeNotifierProvider需要修改和监听的状态(最常用)
Provider只读的静态值
FutureProvider异步获取的数据
StreamProvider持续的数据流
ProxyProvider依赖其他 Provider 的值

完整示例:购物车

dart
// ========== 数据模型 ==========
class CartModel extends ChangeNotifier {
  final List<String> _items = [];

  List<String> get items => List.unmodifiable(_items);
  int get count => _items.length;

  void add(String item) {
    _items.add(item);
    notifyListeners();
  }

  void removeAt(int index) {
    _items.removeAt(index);
    notifyListeners();
  }
}

// ========== 顶层注入 ==========
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => CartModel(),
      child: const MyApp(),
    ),
  );
}

// ========== 显示购物车数量 ==========
class CartBadge extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final cart = context.watch<CartModel>();
    return Text('购物车 (${cart.count})');
  }
}

// ========== 添加商品按钮 ==========
class AddToCartButton extends StatelessWidget {
  final String item;
  const AddToCartButton({super.key, required this.item});

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () => context.read<CartModel>().add(item),
      child: const Text('加入购物车'),
    );
  }
}

常见错误排查

现象原因解决
数据改了但 UI 没更新忘记调用 notifyListeners()在修改数据的方法末尾加上
报错 ProviderNotFoundException没在顶层注入,或类型写错了检查 ChangeNotifierProvider 是否在父级
按钮点击后整个页面重建在回调中用了 watch改用 read

四、Riverpod — Provider 的升级版

Riverpod 是 Provider 的「升级版」,由 Provider 的同一作者开发。它修复了 Provider 的设计缺陷,提供了更强的类型安全和更灵活的状态管理能力。

Riverpod = River + Pod,是 Provider 的字母重排,意思是「更好的 Provider」。

安装与配置

bash
flutter pub add flutter_riverpod

配置 riverpod_lint(推荐)——在编写代码时实时检测常见错误:

yaml
# analysis_options.yaml
include: package:flutter_lints/flutter.yaml

plugins:
  riverpod_lint: 3.1.3

三个核心概念

概念作用类比
Provider定义一个状态(数据 + 计算逻辑)变量的「定义」
ref读取或监听其他 Provider变量的「引用」
ProviderScope在顶层包裹整个 App状态的「容器」

快速上手:计数器

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

void main() {
  runApp(
    const ProviderScope(  // Riverpod 的根容器
      child: MyApp(),
    ),
  );
}

// 定义 Notifier 类(Riverpod 3.x 推荐写法)
class CounterNotifier extends Notifier<int> {
  @override
  int build() => 0;  // 初始值

  void increment() => state++;
}

// 声明 Provider 变量
final counterProvider = NotifierProvider<CounterNotifier, int>(
  CounterNotifier.new,
);

// 在 Widget 中使用
class CounterPage extends ConsumerWidget {
  const CounterPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);  // 监听变化

    return Scaffold(
      appBar: AppBar(title: const Text('Riverpod 计数器')),
      body: Center(
        child: Text('计数: $count', style: const TextStyle(fontSize: 48)),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => ref.read(counterProvider.notifier).increment(),
        child: const Icon(Icons.add),
      ),
    );
  }
}

核心规则:watch vs read

场景用什么原因
build显示数据ref.watch()数据变了需要重建 UI
在按钮点击中修改数据ref.read()不需要重建按钮本身
在事件回调中读取一次ref.read()只需要当前值

一句话记忆

watch 用于「看」,read 用于「做」。

ConsumerWidget vs ConsumerStatefulWidget

Riverpod Widget对应 Flutter Widget何时使用
ConsumerWidgetStatelessWidget只需要读取共享状态
ConsumerStatefulWidgetStatefulWidget既有共享状态,又有自己的内部状态

Provider 类型速查

Provider 类型适用场景典型用例
Provider只读值、计算值主题配置、格式化数据
NotifierProvider同步状态 + 业务逻辑计数器、购物车
AsyncNotifierProvider异步初始化的状态从本地存储加载配置
FutureProvider一次性异步数据从 API 获取数据
StreamProvider持续产生的数据流WebSocket、计时器

NotifierProvider — 同步状态管理

dart
class CartNotifier extends Notifier<List<Product>> {
  @override
  List<Product> build() => [];  // 初始状态

  void add(Product product) => state = [...state, product];
  void remove(int index) => state = [...state]..removeAt(index);
  void clear() => state = [];
}

final cartProvider = NotifierProvider<CartNotifier, List<Product>>(
  CartNotifier.new,
);

不可变更新

在 Notifier 中更新状态时,必须创建新对象(state = [...state, item]),而不是直接修改(state.add(item) ❌)。直接修改不会触发 UI 刷新。

AsyncNotifierProvider — 异步初始化状态

dart
class SettingsNotifier extends AsyncNotifier<ThemeMode> {
  @override
  Future<ThemeMode> build() async {
    await Future.delayed(const Duration(seconds: 1));
    return ThemeMode.system;
  }

  Future<void> setTheme(ThemeMode mode) async {
    state = AsyncData(mode);
  }
}

final settingsProvider = AsyncNotifierProvider<SettingsNotifier, ThemeMode>(
  SettingsNotifier.new,
);

// 使用 AsyncValue.when() 处理三种状态
class SettingsPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final settingsState = ref.watch(settingsProvider);

    return settingsState.when(
      loading: () => const Center(child: CircularProgressIndicator()),
      error: (err, _) => Text('加载失败: $err'),
      data: (mode) => SwitchListTile(
        title: const Text('深色模式'),
        value: mode == ThemeMode.dark,
        onChanged: (isDark) {
          ref.read(settingsProvider.notifier).setTheme(
            isDark ? ThemeMode.dark : ThemeMode.light,
          );
        },
      ),
    );
  }
}

修饰符

autoDispose — 自动释放:

当没有 Widget 监听时,自动销毁 Provider 并释放资源。

dart
final searchProvider = FutureProvider.autoDispose<List<String>>((ref) async {
  final query = ref.watch(queryProvider);
  return apiSearch(query);
});

family — 接收参数:

让 Provider 根据外部参数生成不同的实例:

dart
final articleDetailProvider = FutureProvider.autoDispose.family<String, String>(
  (ref, articleId) async {
    return '这是 $articleId 的详情内容';
  },
);

// 使用时传入参数
final detail = ref.watch(articleDetailProvider('article_001'));

组合使用顺序:先 autoDispose,再 family

Provider 之间互相依赖

dart
final productsProvider = Provider<List<Product>>((ref) => [...]);

final cartProvider = NotifierProvider<CartNotifier, List<Product>>(CartNotifier.new);

// 总价 Provider —— 依赖购物车,自动计算
final totalPriceProvider = Provider<int>((ref) {
  final cart = ref.watch(cartProvider);
  return cart.fold<int>(0, (sum, item) => sum + item.price);
});

代码生成方式(进阶)

Riverpod 支持代码生成写法,用注解代替手写模板代码:

dart
part 'counter.g.dart';

@riverpod
class Counter extends _$Counter {
  @override
  int build() => 0;
  void increment() => state++;
}
// counterProvider 自动生成在 counter.g.dart 中

安装:

bash
flutter pub add riverpod_annotation
flutter pub add dev:riverpod_generator
flutter pub add dev:build_runner

生成代码:

bash
dart run build_runner build    # 一次性生成
dart run build_runner watch    # 监听变化,自动重新生成

Riverpod vs Provider 快速对比

对比项ProviderRiverpod
定义状态继承 ChangeNotifierProvider / NotifierProvider
注入方式Widget 树中用 Provider 包裹顶层 ProviderScope,Provider 定义在外部
读取方式context.watch() / context.read()ref.watch() / ref.read()
Widget 基类StatelessWidget / StatefulWidgetConsumerWidget / ConsumerStatefulWidget
依赖 BuildContext
编译时安全
同类型多个 Provider冲突通过名称区分

五、GetX — 追求极简的全能框架

GetX 是一个多功能框架,把状态管理、路由导航、依赖注入三大功能打包在一起,主打——代码量极少,上手极简。

安装

bash
flutter pub add get

MaterialApp 替换为 GetMaterialApp

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

void main() {
  runApp(const GetMaterialApp(home: MyApp()));
}

两种响应式方式

方式核心思路适用场景
.obs + Obx数据变了自动刷新简单状态,需要精细更新
GetBuilder + update()手动调用 update() 刷新不需要自动监听,手动控制更新

响应式状态管理(.obs)

.obs 把普通变量变成「可观察的」,Obx 会自动响应变化:

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

// ─── ★ 定义控制器 ──────────────
class CounterController extends GetxController {
  final count = 0.obs;  // .obs 把 int 变成可观察的响应式变量

  void increment() => count++;  // 修改后自动通知 UI
}
// ─── ☆ 定义控制器 ──────────────

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

  final controller = Get.put(CounterController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('GetX 计数器')),
      body: Center(
        // ─── ★ Obx 监听变化 ──────────────
        child: Obx(() => Text(
          '计数: ${controller.count.value}',
          style: const TextStyle(fontSize: 48),
        )),
        // ─── ☆ Obx 监听变化 ──────────────
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: controller.increment,
        child: const Icon(Icons.add),
      ),
    );
  }
}

.obs 类型与读写

dart
// 基本类型
final name = ''.obs;          // RxString
final count = 0.obs;          // RxInt
final price = 0.0.obs;        // RxDouble
final isActive = false.obs;   // RxBool

// 集合类型
final items = <String>[].obs;          // RxList<String>
final user = <String, dynamic>{}.obs;  // RxMap

// 读取值:用 .value
print(count.value);   // 0

// 修改值
count.value = 10;          // 直接赋值
count++;                   // RxInt 支持直接运算
items.add('苹果');          // RxList 支持直接操作

自定义对象必须用 .value 或 update 修改

dart
// ❌ 错误:修改属性不会触发更新
final user = User(name: '张三', age: 25).obs;
user.value.name = '李四';  // UI 不会更新!

// ✅ 正确:创建新对象赋值
user.value = User(name: '李四', age: 25);

// ✅ 正确:使用 update 方法
user.update((val) { val!.name = '李四'; });

简单状态管理(GetBuilder)

GetBuilder 不会自动监听,需要手动调用 update()

dart
class CounterController extends GetxController {
  int count = 0;  // 普通 int,不是 .obs

  void increment() {
    count++;
    update();  // 👈 手动通知 UI 刷新
  }
}

// 在 Widget 中使用
GetBuilder<CounterController>(
  builder: (ctrl) => Text('计数: ${ctrl.count}'),
),

update() 支持条件刷新,传入 ID 只刷新对应的 GetBuilder

dart
// 控制器中
update(['name']);  // 只刷新监听 'name' 的 GetBuilder

// Widget 中
GetBuilder<UserController>(
  id: 'name',  // 👈 只响应 update(['name'])
  builder: (ctrl) => Text('姓名: ${ctrl.name}'),
),

Obx vs GetBuilder

ObxGetBuilder
触发方式.obs 变量变化自动触发手动调用 update()
数据类型必须用 .obs 包装普通变量即可
性能略低(响应式有额外开销)略高(无自动监听开销)
适用数据频繁变化、需要自动响应不频繁变化、手动控制刷新

一句话选择

数据经常变、想让 UI 自动跟着变 → Obx;想自己控制什么时候刷新 → GetBuilder

GetxController 生命周期

dart
class MyController extends GetxController {
  @override
  void onInit() {
    super.onInit();
    // 控制器初始化时调用(类似 initState)
  }

  @override
  void onReady() {
    super.onReady();
    // Widget 渲染完成后调用,适合做数据请求
  }

  @override
  void onClose() {
    // 控制器被销毁时调用(类似 dispose)
    super.onClose();
  }
}

获取控制器的方式

方式何时创建实例数量适用场景
Get.put立即单例大多数场景
Get.lazyPut首次 find单例不确定是否会用到
Get.find不创建,只查找获取已注册的控制器
Get.create每次获取时多实例需要独立实例的场景

页面级绑定(推荐)

使用 Bindings 让控制器与页面绑定——页面打开时创建,关闭时销毁:

dart
class HomeBinding extends Bindings {
  @override
  void dependencies() {
    Get.lazyPut<HomeController>(() => HomeController());
  }
}

// 在路由中绑定
Get.to(() => const HomePage(), binding: HomeBinding());

GetView 简化

如果一个页面只使用一个控制器,可以用 GetView 省去手动获取:

dart
// ─── ★ GetView 简化 ──────────────
class HomePage extends GetView<HomeController> {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    // 直接用 controller,不需要 Get.put 或 Get.find
    return Obx(() => Text('计数: ${controller.count.value}'));
  }
}
// ─── ☆ GetView 简化 ──────────────

GetX 路由导航

GetX 自带路由管理,不依赖 BuildContext

dart
// 跳转到新页面
Get.to(() => const SecondPage());

// 带参数跳转
Get.to(() => const DetailPage(), arguments: {'id': 42});

// 在目标页面获取参数
final args = Get.arguments;  // {'id': 42}

// 返回上一页
Get.back();

// 替换当前页面
Get.off(() => const LoginPage());

// 清空所有页面并跳转(适合登录后跳主页)
Get.offAll(() => const HomePage());

完整示例:待办事项 App

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

void main() {
  runApp(const GetMaterialApp(home: TodoPage()));
}

// ============ 数据模型 ============

class Todo {
  final String title;
  final bool done;
  const Todo({required this.title, this.done = false});
  Todo copyWith({String? title, bool? done}) =>
      Todo(title: title ?? this.title, done: done ?? this.done);
}

// ============ 状态管理 ============

class TodoController extends GetxController {
  final todos = <Todo>[].obs;
  int get uncompletedCount => todos.where((t) => !t.done).length;

  void add(String title) => todos.add(Todo(title: title));

  void toggle(int index) {
    todos[index] = todos[index].copyWith(done: !todos[index].done);
  }

  void remove(int index) => todos.removeAt(index);
}

// ============ UI ============

class TodoPage extends StatelessWidget {
  const TodoPage({super.key});

  final controller = Get.put(TodoController());
  final _inputController = TextEditingController();

  void _addTodo() {
    final text = _inputController.text.trim();
    if (text.isEmpty) return;
    controller.add(text);
    _inputController.clear();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Obx(() => Text('待办事项 (${controller.uncompletedCount} 未完成)')),
      ),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(12),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _inputController,
                    decoration: const InputDecoration(
                      hintText: '输入待办事项...',
                      border: OutlineInputBorder(),
                    ),
                    onSubmitted: (_) => _addTodo(),
                  ),
                ),
                const SizedBox(width: 8),
                ElevatedButton(onPressed: _addTodo, child: const Text('添加')),
              ],
            ),
          ),
          Expanded(
            child: Obx(() {
              final todos = controller.todos;
              if (todos.isEmpty) {
                return const Center(child: Text('暂无待办事项'));
              }
              return ListView.builder(
                itemCount: todos.length,
                itemBuilder: (context, index) {
                  final todo = todos[index];
                  return ListTile(
                    leading: Checkbox(
                      value: todo.done,
                      onChanged: (_) => controller.toggle(index),
                    ),
                    title: Text(
                      todo.title,
                      style: TextStyle(
                        decoration: todo.done
                            ? TextDecoration.lineThrough
                            : TextDecoration.none,
                      ),
                    ),
                    trailing: IconButton(
                      icon: const Icon(Icons.delete),
                      onPressed: () => controller.remove(index),
                    ),
                  );
                },
              );
            }),
          ),
        ],
      ),
    );
  }
}

常见错误排查

现象原因解决
Obx 内没用 .obs 变量报错Obx builder 中必须至少使用一个 .obs 变量如果不需要响应式,直接用普通 Widget
控制器获取报错 "Controller not found"还没注册就获取Get.put()Get.find()
自定义对象修改后 UI 不更新只修改了属性,没有整体赋值.value = 新对象.update()
页面退出后控制器还在内存没有使用 Bindings 或手动删除使用页面级绑定 Get.to(..., binding: ...)

状态管理方案对比

方案优点缺点
setState简单直接,无需额外依赖无法跨组件共享,层层回调麻烦
InheritedWidgetFlutter 内置,理解原理重要手写繁琐,实际项目不推荐直接使用
Provider官方推荐,轻量易学需要理解 BuildContext,大型项目不够强
RiverpodProvider 的改进版,编译时安全,不依赖 Context学习曲线稍高,API 变化较多
Bloc / Cubit严格的架构模式,适合团队协作样板代码多,学习成本高
GetX开发速度快,功能全面,样板代码极少不够规范,长期维护不确定

核心原则

不要过度设计。如果 setState 能解决问题,就不要引入 Provider。如果 Provider 够用,就不需要 Riverpod。如果追求开发速度,GetX 是最快的选择,但要注意规范性。

下一步

基于 Flutter 官方文档整理