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

Flutter使用sms_autofill设置Pin遇双报错的解决方法咨询

解决sms_autofill的PinFieldAutoFill组件两类问题

问题1:setState() or markNeedsBuild() called during build

原因

组件构建过程中,PinFieldAutoFill的onCodeChanged可能因autoFocus: true被立即触发,此时直接调用setState会违反Flutter生命周期规则,导致在build未完成时触发状态更新。

解决方法

将setState延迟到当前构建帧结束后执行,使用WidgetsBinding.instance.addPostFrameCallback包裹,并增加mounted判断避免组件销毁后触发错误:

修改_updatePin方法:

void _updatePin(String val) {
  if (!isPinSet) {
    pin = val;
    if (pin.length == 4) {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (mounted) {
          setState(() {
            isPinSet = true;
            _otpTextController.clear();
          });
        }
      });
    }
  } else {
    confirmedPin = val;
  }
}

问题2:Each child must be laid out exactly once

原因

isPinSet状态切换时,PinFieldAutoFill内部子组件可能因身份识别模糊被重复布局;同时复用同一个TextEditingController在设置/确认PIN两个场景下,易引发状态冲突。

解决方法

  1. 给PinFieldAutoFill添加唯一Key,帮助Flutter识别组件身份:
Center(
  child: PinFieldAutoFill(
    key: ValueKey(isPinSet), // 根据状态生成唯一Key
    // 其他属性保持不变
  ),
)
  1. 切换状态时清空TextEditingController,避免残留文本干扰新输入逻辑(已包含在上述_updatePin的修改中)。

  2. 优化按钮可用状态判断,确保只有PIN和确认PIN都符合长度要求时才启用:

bool isButtonEnabled = (pin == confirmedPin && pin.length == 4 && confirmedPin.length == 4);

完整修改后代码

class _SetPinScreenState extends State<SetPinScreen> {
  bool obscureText = true;
  TextEditingController _otpTextController = TextEditingController();
  bool isPinSet = false;
  String pin = '', confirmedPin = '';

  void _updatePin(String val) {
    if (!isPinSet) {
      pin = val;
      if (pin.length == 4) {
        WidgetsBinding.instance.addPostFrameCallback((_) {
          if (mounted) {
            setState(() {
              isPinSet = true;
              _otpTextController.clear();
            });
          }
        });
      }
    } else {
      confirmedPin = val;
    }
  }

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

  @override
  Widget build(BuildContext context) {
    bool isButtonEnabled = (pin == confirmedPin && pin.length == 4 && confirmedPin.length ==4);
    return MainTemplate(
      isFloatingButtonVisible: true,
      isFloatingActionButtonDisabled: !isButtonEnabled,
      floatingButtonAction: () {
        if (isButtonEnabled) {
          print("Floating action button pressed");
        }
      },
      child: Padding(
        padding: const EdgeInsets.all(15.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Align(
              alignment: Alignment.centerLeft,
              child: Text(
                isPinSet
                    ? 'Re-enter your PIN code to Confirm'
                    : 'Set PIN for your Card Ending With XXXX',
                style: TextStyle(fontWeight: FontWeight.w700, fontSize: 30),
              ),
            ),
            SizedBox(height: 20),
            Center(
              child: PinFieldAutoFill(
                key: ValueKey(isPinSet),
                decoration: UnderlineDecoration(
                  textStyle: Theme.of(context).textTheme.subtitle1.copyWith(
                        fontWeight: FontWeight.bold,
                        fontSize: 15,
                      ),
                  gapSpace: 8,
                  obscureStyle: ObscureStyle(isTextObscure: obscureText),
                  lineHeight: 1.2,
                  colorBuilder:
                      PinListenColorBuilder(Colors.black, Colors.grey.shade400),
                ),
                onCodeChanged: _updatePin,
                codeLength: 4,
                autoFocus: true,
                controller: _otpTextController,
                keyboardType: TextInputType.number,
              ),
            ),
            SizedBox(height: 20),
            Text(
              'Pin must be at least 4 digits. It must not be single digits like'
              ' 1111 or digits in numerical order such as 1234',
              style: TextStyle(
                color: Colors.black,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

额外说明

原代码中isFloatingActionButtonDisabled: isButtonEnabled逻辑可能存在反向问题,已调整为!isButtonEnabled(即按钮可用时禁用状态为false),若原逻辑为业务需求可忽略此修改。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 21:30:34