Flutter表单提交报错:Null check operator used on a null value
Flutter表单提交出现Null check错误的解决方法
问题场景
在Flutter项目中使用控制器实现表单数据插入数据库功能,点击提交按钮时触发_CastError,提示“Null check operator used on a null value”。确认控制器已赋值,但尝试打印控制器值前就报错,怀疑问题出在提交回调或表单组件中,目标是实现表单数据成功插入数据库。
错误信息
处理手势时抛出以下_CastError: Null check operator used on a null value 抛出异常时的调用栈: #0 _FormPageState.build.<anonymous closure> (package:testerlogin/pages/formpage.dart:187:54) #1 _InkResponseState.handleTap (package:flutter/src/material/ink_well.dart:1072:21) #2 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:253:24) #3 TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:627:11) #4 BaseTapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:306:5) #5 BaseTapGestureRecognizer.acceptGesture (package:flutter/src/gestures/tap.dart:276:7) #6 GestureArenaManager.sweep (package:flutter/src/gestures/arena.dart:163:27) #7 GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:464:20) #8 GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:440:22) #9 RendererBinding.dispatchEvent (package:flutter/src/rendering/binding.dart:337:11) #10 GestureBinding._handlePointerEventImmediately (package:flutter/src/gestures/binding.dart:395:7) #11 GestureBinding.handlePointerEvent (package:flutter/src/gestures/binding.dart:357:5) #12 GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:314:7) #13 GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:295:7) #14 _invoke1 (dart:ui/hooks.dart:167:13) #15 PlatformDispatcher._dispatchPointerDataPacket (dart:ui/platform_dispatcher.dart:341:7) #16 _dispatchPointerDataPacket (dart:ui/hooks.dart:94:31) Handler: "onTap" Recognizer: TapGestureRecognizer#75718
原始代码
import 'package:flutter/material.dart'; import 'package:testerlogin/models/flowerbook.dart'; import 'package:testerlogin/models/user.dart'; import '../common_widgets/buttons.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import '../common_widgets/checkbox.dart'; class FormPage extends StatefulWidget { const FormPage({Key? key, required this.user}) : super(key: key); final User user; @override State<FormPage> createState() => _FormPageState(); } class _FormPageState extends State<FormPage> { final GlobalKey<FormBuilderState> _formKey = GlobalKey<FormBuilderState>(); String selectedValue = 'Bouquet'; final dateController = TextEditingController(); final checkboxController = TextEditingController(); final dropdownController = TextEditingController(); final TextStyle labelStyle = const TextStyle( fontFamily: 'Poppins', fontWeight: FontWeight.w500, ); final TextStyle inputStyle = const TextStyle( fontFamily: 'Poppins', fontWeight: FontWeight.w600, ); @override Widget build(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; return Scaffold( body: SingleChildScrollView( child: Form( key: _formKey, child: SafeArea( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ //Spacing const SizedBox( height: 25, ), //Back Button Container( height: 50, padding: const EdgeInsets.fromLTRB(2 * 16, 0, 2 * 16, 0), width: double.infinity, child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ IconButton( onPressed: () { Navigator.pop(context); }, icon: const Icon(Icons.arrow_back), ), ], ), ), const SizedBox( height: 25, ), SizedBox( width: screenWidth, child: Padding( padding: const EdgeInsets.fromLTRB(3 * 16, 0, 3 * 16, 3 * 16), child: Column( children: [ //Checkbox FormField(builder: (FormFieldState state) { return InputDecorator( decoration: InputDecoration( labelText: "Delivery Date", labelStyle: labelStyle, errorText: state.errorText, border: const OutlineInputBorder(), floatingLabelBehavior: FloatingLabelBehavior.always, ), child: FormBuilderDateTimePicker( controller: dateController, name: "delivery_date", )); }), const SizedBox(height: 25), FormField( builder: (FormFieldState state) { return InputDecorator( decoration: InputDecoration( labelText: "Flower Type", labelStyle: labelStyle, errorText: state.errorText, border: const OutlineInputBorder(), floatingLabelBehavior: FloatingLabelBehavior.always, ), child: Column( children: <Widget>[ checkItemBuilder(context, "Roses", checkboxController: checkboxController), checkItemBuilder(context, "Tulips", checkboxController: checkboxController), checkItemBuilder(context, "Carnation", checkboxController: checkboxController), checkItemBuilder(context, "Daisies", checkboxController: checkboxController), checkItemBuilder(context, "Tulips", checkboxController: checkboxController), ], ), ); }, ), const SizedBox(height: 25), SizedBox( width: MediaQuery.of(context).size.width, child: FormField( builder: (FormFieldState state) { return InputDecorator( decoration: InputDecoration( labelText: "Arrangement", labelStyle: labelStyle, errorText: state.errorText, border: const OutlineInputBorder(), floatingLabelBehavior: FloatingLabelBehavior.always, ), child: DropdownButton<String>( value: selectedValue, isExpanded: true, onChanged: (String? newValue) { setState(() { selectedValue = newValue ?? 'Bouquet'; dropdownController.text = selectedValue; }); }, items: <String>[ 'Bouquet', 'Vase', 'Box', 'Basket' ].map<DropdownMenuItem<String>>( (String value) { return DropdownMenuItem<String>( value: value, child: Text(value), ); }).toList(), ), ); }, ), ), const SizedBox(height: 25), //Submit Button ButtonGradient( label: 'Submit', textStyle: const TextStyle( fontFamily: 'Poppins', fontWeight: FontWeight.w600, color: Colors.white, ), borderRadius: 50, gradientColor: const [ Colors.purpleAccent, Colors.deepPurple, ], onPressed: () { if (_formKey.currentState!.validate()) { Flowerbook flowerbook = Flowerbook( deliver_date: dateController.text, flower_type: checkboxController.text, arrangement: dropdownController.text, user_id: widget.user.user_id, ); Flowerbook.register(flowerbook); } }, ), ], ), ), ), ], ), ), ), ), ); } }
问题分析
- 表单Key类型不匹配:代码中定义的
_formKey是GlobalKey<FormBuilderState>,但实际使用的是Flutter原生的Form组件,而非FormBuilder组件,导致_formKey.currentState为null,使用!空检查运算符时触发错误。 - FormField未实现验证逻辑:所有自定义的
FormField都没有提供validator回调,导致validate()方法无法正常工作,也无法触发表单验证。 - FormBuilder组件与原生Form混用:使用了
FormBuilderDateTimePicker但外层是原生Form,组件间不兼容,无法正常关联表单状态。
解决方案
方案1:使用原生Form组件(修正Key类型)
将_formKey的类型改为GlobalKey<FormState>,并为每个FormField添加验证逻辑:
class _FormPageState extends State<FormPage> { // 修正为FormState的Key final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); String selectedValue = 'Bouquet'; final dateController = TextEditingController(); final checkboxController = TextEditingController(); final dropdownController = TextEditingController(); // ... 其他代码不变 @override Widget build(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; return Scaffold( body: SingleChildScrollView( child: Form( key: _formKey, child: SafeArea( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // ... 其他代码不变 // 修正Delivery Date的FormField,添加验证 FormField<String>( validator: (value) { if (dateController.text.isEmpty) { return '请选择配送日期'; } return null; }, builder: (FormFieldState<String> state) { return InputDecorator( decoration: InputDecoration( labelText: "Delivery Date", labelStyle: labelStyle, errorText: state.errorText, border: const OutlineInputBorder(), floatingLabelBehavior: FloatingLabelBehavior.always, ), // 替换为原生DateTimePicker,避免FormBuilder组件冲突 child: TextField( controller: dateController, readOnly: true, onTap: () async { DateTime? pickedDate = await showDatePicker( context: context, initialDate: DateTime.now(), firstDate: DateTime(2000), lastDate: DateTime(2101), ); if (pickedDate != null) { setState(() { dateController.text = "${pickedDate.day}/${pickedDate.month}/${pickedDate.year}"; }); } }, )); }, ), const SizedBox(height: 25), // 修正Flower Type的FormField,添加验证 FormField<String>( validator: (value) { if (checkboxController.text.isEmpty) { return '请选择至少一种花卉类型'; } return null; }, builder: (FormFieldState<String> state) { return InputDecorator( decoration: InputDecoration( labelText: "Flower Type", labelStyle: labelStyle, errorText: state.errorText, border: const OutlineInputBorder(), floatingLabelBehavior: FloatingLabelBehavior.always, ), child: Column( children: <Widget>[ checkItemBuilder(context, "Roses", checkboxController: checkboxController), checkItemBuilder(context, "Tulips", checkboxController: checkboxController), checkItemBuilder(context, "Carnation", checkboxController: checkboxController), checkItemBuilder(context, "Daisies", checkboxController: checkboxController), ], ), ); }, ), const SizedBox(height: 25), // 修正Arrangement的FormField,添加验证 FormField<String>( validator: (value) { if (dropdownController.text.isEmpty) { return '请选择花束类型'; } return null; }, builder: (FormFieldState<String> state) { return InputDecorator( decoration: InputDecoration( labelText: "Arrangement", labelStyle: labelStyle, errorText: state.errorText, border: const OutlineInputBorder(), floatingLabelBehavior: FloatingLabelBehavior.always, ), child: DropdownButton<String>( value: selectedValue, isExpanded: true, onChanged: (String? newValue) { setState(() { selectedValue = newValue ?? 'Bouquet'; dropdownController.text = selectedValue; }); }, items: <String>[ 'Bouquet', 'Vase', 'Box', 'Basket' ].map<DropdownMenuItem<String>>((String value) { return DropdownMenuItem<String>( value: value, child: Text(value), ); }).toList(), ), ); }, ), const SizedBox(height: 25), //Submit Button ButtonGradient( label: 'Submit', textStyle: const TextStyle( fontFamily: 'Poppins', fontWeight: FontWeight.w600, color: Colors.white, ), borderRadius: 50, gradientColor: const [ Colors.purpleAccent, Colors.deepPurple, ], onPressed: () { // 先检查formKey是否存在,避免空值 if (_formKey.currentState != null && _formKey.currentState!.validate()) { Flowerbook flowerbook = Flowerbook( deliver_date: dateController.text, flower_type: checkboxController.text, arrangement: dropdownController.text, user_id: widget.user.user_id ?? 0, // 处理user_id可能为null的情况 ); Flowerbook.register(flowerbook); // 提交成功后可以添加提示或跳转 ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('提交成功')), ); } }, ), ], ), ), ), ), ); } }
方案2:使用FormBuilder组件(保持Key类型)
如果要继续使用FormBuilder组件,需要将外层的Form替换为FormBuilder,并调整组件用法:
class _FormPageState extends State<FormPage> { final GlobalKey<FormBuilderState> _formKey = GlobalKey<FormBuilderState>(); String selectedValue = 'Bouquet'; // 可以不需要单独的控制器,FormBuilder会自动管理状态 final checkboxController = TextEditingController(); // ... 其他代码不变 @override Widget build(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; return Scaffold( body: SingleChildScrollView( // 替换为FormBuilder child: FormBuilder( key: _formKey, child: SafeArea( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // ... 其他代码不变 // 使用FormBuilderDateTimePicker FormBuilderDateTimePicker( name: "delivery_date", decoration: InputDecoration( labelText: "Delivery Date", labelStyle: labelStyle, border: const OutlineInputBorder(), floatingLabelBehavior: FloatingLabelBehavior.always, ), inputType: InputType.date, ), const SizedBox(height: 25), // 花卉类型可以用FormBuilderCheckboxGroup FormBuilderCheckboxGroup( name: "flower_type", decoration: InputDecoration( labelText: "Flower Type", labelStyle: labelStyle, border: const OutlineInputBorder(), floatingLabelBehavior: FloatingLabelBehavior.always, ), options: const [ FormBuilderFieldOption(value: "Roses"), FormBuilderFieldOption(value: "Tulips"), FormBuilderFieldOption(value: "Carnation"), FormBuilderFieldOption(value: "Daisies"), ], ), const SizedBox(height: 25), // 使用FormBuilderDropdown FormBuilderDropdown( name: "arrangement", decoration: InputDecoration( labelText: "Arrangement", labelStyle: labelStyle, border: const OutlineInputBorder(), floatingLabelBehavior: FloatingLabelBehavior.always, ), initialValue: 'Bouquet',
相关产品推荐
相关产品推荐

