You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter自定义TextField聚焦时触发'!_dirty'断言错误求助

解决TextField聚焦时触发'!_dirty'断言错误的问题

问题分析

你遇到的'!_dirty': is not true断言错误,核心原因是在WidgetStatesController的监听回调中直接调用setState,此时TextField正处于框架的更新流程中(组件标记为dirty),强行触发状态更新会打乱框架的更新顺序,导致断言失败。

解决方案

下面提供两种可行的修复方式,任选其一即可:

方案1:使用FocusNode监听焦点状态(更推荐)

FocusNode是Flutter原生用于监听焦点变化的类,使用它可以避免WidgetStatesController带来的状态冲突问题:

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

  @override
  State<CustomTextField> createState() => _CustomTextFieldState();
}

class _CustomTextFieldState extends State<CustomTextField> {
  late final FocusNode _focusNode;
  bool isFocused = false;

  @override
  void initState() {
    super.initState();
    _focusNode = FocusNode()
      ..addListener(() {
        // 延迟到当前帧渲染完成后更新状态,避免dirty状态冲突
        WidgetsBinding.instance.addPostFrameCallback((_) {
          if (mounted) {
            setState(() {
              isFocused = _focusNode.hasFocus;
            });
          }
        });
      });
  }

  @override
  void dispose() {
    _focusNode.dispose();
    super.dispose();
  }

  @override
  Widget build(final BuildContext context) {
    return Padding(
      padding: isFocused ? const EdgeInsets.all(4) : EdgeInsets.zero,
      child: TextField(
        focusNode: _focusNode,
      ),
    );
  }
}

方案2:保留WidgetStatesController,延迟状态更新

如果坚持使用WidgetStatesController,需要通过addPostFrameCallback延迟状态更新,确保组件更新流程完成后再修改状态:

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

  @override
  State<CustomTextField> createState() => _CustomTextFieldState();
}

class _CustomTextFieldState extends State<CustomTextField> {
  final _widgetStatesController = WidgetStatesController();
  bool isFocused = false;

  @override
  void initState() {
    _widgetStatesController.addListener(() {
      // 延迟到下一帧执行状态更新,避开组件的dirty状态
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (mounted) {
          setState(() {
            isFocused = _widgetStatesController.value.contains(WidgetState.focused);
          });
        }
      });
    });
    super.initState();
  }

  @override
  void dispose() {
    _widgetStatesController.dispose();
    super.dispose();
  }

  @override
  Widget build(final BuildContext context) {
    return Padding(
      padding: isFocused ? const EdgeInsets.all(4) : EdgeInsets.zero,
      child: TextField(
        statesController: _widgetStatesController,
      ),
    );
  }
}

关键修复点说明

  • WidgetsBinding.instance.addPostFrameCallback:确保状态更新操作在当前帧所有组件重建完成后执行,避免组件处于dirty状态时触发setState的冲突。
  • mounted判断:防止组件已被销毁但回调仍在执行的情况,避免空指针或状态更新报错。
  • 固定外层Padding容器:相比动态替换TextField的包裹组件,这种方式让组件树结构更稳定,减少不必要的组件重建。

内容的提问来源于stack exchange,提问作者lsaudon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 08:59:55