Flutter中TextFormField清空输入触发Invalid number异常的解决咨询
问题描述
在Flutter开发中,实现了用户输入数值时的校验逻辑:当销售价格输入框的数值大于原价输入框数值时,弹出SnackBar提示。但清空输入框内容时,会抛出FormatException: Invalid number异常。
异常详情
══╡ EXCEPTION CAUGHT BY WIDGETS ╞═══════════════════════════════════════════════════════════════════ The following FormatException was thrown while calling onChanged: Invalid number (at character 1) ^ When the exception was thrown, this was the stack: #0 int._handleFormatError (dart:core-patch/integers_patch.dart:129:7) #1 int.parse (dart:core-patch/integers_patch.dart:55:14) #2 _GeneralTabState.build.<anonymous closure>.<anonymous closure> (package:shop_app_vendor/screens/add_products/general_tab.dart:247:25) #3 new TextFormField.<anonymous closure>.onChangedHandler (package:flutter/src/material/text_form_field.dart:188:25) #4 EditableTextState._formatAndSetValue (package:flutter/src/widgets/editable_text.dart:2630:27) #5 EditableTextState.userUpdateTextEditingValue (package:flutter/src/widgets/editable_text.dart:2919:5) #6 EditableTextState._replaceText (package:flutter/src/widgets/editable_text.dart:3144:5) #7 CallbackAction.invoke (package:flutter/src/widgets/actions.dart:534:39) #8 ActionDispatcher.invokeAction (package:flutter/src/widgets/actions.dart:573:21) #9 Actions.invoke.<anonymous closure> (package:flutter/src/widgets/actions.dart:872:48) #10 Actions._visitActionsAncestors (package:flutter/src/widgets/actions.dart:653:18) #11 Actions.invoke (package:flutter/src/widgets/actions.dart:866:30) #12 _DeleteTextAction.invoke (package:flutter/src/widgets/editable_text.dart:4042:20) #13 _OverridableContextAction.invokeDefaultAction (package:flutter/src/widgets/actions.dart:1696:28) #14 _OverridableActionMixin.invoke (package:flutter/src/widgets/actions.dart:1559:9) #15 ActionDispatcher.invokeAction (package:flutter/src/widgets/actions.dart:571:21) #16 ShortcutManager.handleKeypress (package:flutter/src/widgets/shortcuts.dart:755:38) #17 _ShortcutsState._handleOnKey (package:flutter/src/widgets/shortcuts.dart:956:20) #18 FocusManager._handleKeyMessage (package:flutter/src/widgets/focus_manager.dart:1687:32) #19 KeyEventManager._dispatchKeyMessage (package:flutter/src/services/hardware_keyboard.dart:828:34) #20 KeyEventManager.handleRawKeyMessage (package:flutter/src/services/hardware_keyboard.dart:875:15) #21 BasicMessageChannel.setMessageHandler.<anonymous closure> (package:flutter/src/services/platform_channel.dart:77:49) #22 BasicMessageChannel.setMessageHandler.<anonymous closure> (package:flutter/src/services/platform_channel.dart:76:47) #23 _DefaultBinaryMessenger.setMessageHandler.<anonymous closure> (package:flutter/src/services/binding.dart:380:35) #24 _DefaultBinaryMessenger.setMessageHandler.<anonymous closure> (package:flutter/src/services/binding.dart:377:46) #25 _invoke2.<anonymous closure> (dart:ui/hooks.dart:190:15) #29 _invoke2 (dart:ui/hooks.dart:189:10) #30 _ChannelCallbackRecord.invoke (dart:ui/channel_buffers.dart:42:5) #31 _Channel.push (dart:ui/channel_buffers.dart:132:31) #32 ChannelBuffers.push (dart:ui/channel_buffers.dart:329:17) #33 PlatformDispatcher._dispatchPlatformMessage (dart:ui/platform_dispatcher.dart:589:22) #34 _dispatchPlatformMessage (dart:ui/hooks.dart:89:31) (elided 3 frames from dart:async) ════════════════════════════════════════════════════════════════════════════════════════════════════
相关代码
scf.dart
class SCF { scaffoldMsg({context, msg}) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text(msg), backgroundColor: Colors.red, action: SnackBarAction( label: 'OK', textColor: Colors.white, onPressed: () { ScaffoldMessenger.of(context).clearSnackBars(); }, ), )); } }
general_tab.dart
class GeneralTab extends StatefulWidget { const GeneralTab({Key? key}) : super(key: key); @override State<GeneralTab> createState() => _GeneralTabState(); } class _GeneralTabState extends State<GeneralTab> { final SCF _scf = SCF(); @override Widget build(BuildContext context) { return Consumer<ProductProvider>( builder: (context, provider, child) { return ListView( padding: const EdgeInsets.all(15.0), children: [ // Regular Price FormFieldInput( label: 'Regular Price', inputType: TextInputType.number, onChanged: (value) { provider.getFormData(regularPrice: int.parse(value)); }, ), // Sales Price FormFieldInput( label: 'Sales Price', inputType: TextInputType.number, onChanged: (value) { if (int.parse(value) > provider.productData!['regularPrice']) { _scf.scaffoldMsg( context: context, msg: 'Sales price should be less than regular price', ); } setState(() { provider.getFormData(salesPrice: int.parse(value)); }); }, ), ], ); }, ); } }
product_provider.dart
class ProductProvider with ChangeNotifier { Map<String, dynamic>? productData = {}; getFormData({ int? regularPrice, int? salesPrice, }) { if (regularPrice != null) { productData!['regularPrice'] = regularPrice; } if (salesPrice != null) { productData!['salesPrice'] = salesPrice; } notifyListeners(); } }
form_field.dart
class FormFieldInput extends StatelessWidget { final String? label; final void Function(String)? onChanged; final TextInputType? inputType; const FormFieldInput({ Key? key, this.label, this.onChanged, }) : super(key: key); @override Widget build(BuildContext context) { return TextFormField( decoration: InputDecoration( label: Text(label!), ), onChanged: onChanged, ); } }
修复方案
问题根源是清空输入框时value为空字符串,调用int.parse('')会抛出格式异常。需要在解析前先校验输入内容是否有效,同时处理空值和非数值情况。
步骤1:添加安全的数值解析工具方法
封装一个工具方法,处理空字符串和无效数值的情况,返回可空的整数:
int? tryParseInt(String? value) { if (value == null || value.trim().isEmpty) return null; return int.tryParse(value.trim()); }
步骤2:修改输入框的onChanged逻辑
在general_tab.dart中使用上面的工具方法,避免直接调用int.parse:
// Regular Price的onChanged修改为 onChanged: (value) { final price = tryParseInt(value); provider.getFormData(regularPrice: price); }, // Sales Price的onChanged修改为 onChanged: (value) { final salesPrice = tryParseInt(value); final regularPrice = provider.productData?['regularPrice']; // 只有当两个价格都不为空时才进行校验 if (salesPrice != null && regularPrice != null && salesPrice > regularPrice) { _scf.scaffoldMsg( context: context, msg: 'Sales price should be less than regular price', ); } provider.getFormData(salesPrice: salesPrice); // 这里不需要setState,因为provider的notifyListeners会触发Consumer重建 },
步骤3:调整Provider的逻辑(可选)
如果允许价格为空,可以保持现有逻辑;如果需要默认值,可在getFormData中处理:
// product_provider.dart中修改getFormData getFormData({ int? regularPrice, int? salesPrice, }) { if (regularPrice != null) { productData!['regularPrice'] = regularPrice; } else { // 可选:清空或设置默认值 productData!.remove('regularPrice'); // 或者 productData!['regularPrice'] = 0; } if (salesPrice != null) { productData!['salesPrice'] = salesPrice; } else { productData!.remove('salesPrice'); // 或者 productData!['salesPrice'] = 0; } notifyListeners(); }
步骤4:完善FormFieldInput(可选)
在form_field.dart中启用数字键盘,避免用户输入非数字字符:
@override Widget build(BuildContext context) { return TextFormField( decoration: InputDecoration( label: Text(label!), ), keyboardType: inputType ?? TextInputType.text, // 应用传入的键盘类型 inputFormatters: inputType == TextInputType.number ? [FilteringTextInputFormatter.digitsOnly] // 只允许输入数字 : null, onChanged: onChanged, ); }
内容的提问来源于stack exchange,提问作者Kvu
相关产品推荐
相关产品推荐

