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两个场景下,易引发状态冲突。
解决方法
- 给
PinFieldAutoFill添加唯一Key,帮助Flutter识别组件身份:
Center( child: PinFieldAutoFill( key: ValueKey(isPinSet), // 根据状态生成唯一Key // 其他属性保持不变 ), )
切换状态时清空
TextEditingController,避免残留文本干扰新输入逻辑(已包含在上述_updatePin的修改中)。优化按钮可用状态判断,确保只有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
相关产品推荐
相关产品推荐

