客户端开发

Obx 里读了 Rx,为什么仍提示 improper use?

GetX 仅在 Obx 回调同步执行栈内追踪响应式变量;当读取被 Builder 或 ListView.builder 推迟到后续构建阶段时即会报错。

在维护使用 GetX 的 Flutter 页面时,偶尔会遇到页面构建直接抛出异常:

[Get] the improper use of a GetX has been detected.
You should only use GetX or Obx for the specific widget that will be updated.

检查报错位置的 Obx(() { ... }),代码块里已经包含了 controller.someRx.value,初看容易怀疑是 Controller 注册顺序或响应式字段声明有误。

原因:同步追踪栈被延迟回调隔断

Obx 并不是通过静态语法扫描来识别响应式依赖的,而是在执行它自己的 builder 闭包前打开一个临时的全局监听上下文,把本次函数同步调用栈期间触发了 getter 的 Rx 变量登记为当前 Widget 的依赖,函数返回后立即关闭监听。

如果 Obx 直接返回了一个 Builder、LayoutBuilder 或 ListView.builder,而 .value 的读取全部写在内层 builder 回调里:

// 错误写法:Obx 同步执行时只创建了 Builder 对象,并未真正执行内层闭包
return Obx(() {
  return Builder(
    builder: (context) {
      final status = controller.vipStatus.value;
      return Text(status.label);
    },
  );
});

在 Obx 自己的闭包返回那一刻,内层 Builder.builder 还没有被 Flutter 框架调用。对 GetX 而言,这次构建期间捕获到的可观察对象数量为 0,于是直接判定为 improper use。

修复对照

把需要监听的 Rx 变量提到 Obx 的直接同步作用域内读取,再把读出的普通值传给内层组件即可:

// 正确写法:在 Obx 直接执行栈内完成 Rx 读取
return Obx(() {
  final status = controller.vipStatus.value;
  return Builder(
    builder: (context) {
      return Text(status.localizedLabel(context));
    },
  );
});

同理,在搭配 ListView.builder 时,如果外层需要根据列表增删重绘,也必须在 Obx 顶层先读取 itemCount: controller.items.length,而不能只在 itemBuilder 回调里访问 controller.items[index]:

return Obx(() {
  final items = controller.items;
  return ListView.builder(
    itemCount: items.length,
    itemBuilder: (context, index) {
      final item = items[index];
      return Text(item.title);
    },
  );
});

如果内层套 Builder 只是为了获取 BuildContext,直接从外层方法传入 context 会更简洁;若单个列表项内部还有独立高频变化的字段,可以将小范围的 Obx 放到列表项内部,避免用一个外层 Obx 包裹整棵页面子树。