Flutter中多Widget能否共用同一GlobalKey?报错求助
问题描述
我是Flutter新手,尝试在包含FormHelper子组件的表单中动态添加带TextFormField的Section。为了实现验证和保存功能,我用GlobalKey访问FormState,但添加新Section时触发以下错误:
════════ Exception caught by widgets library ═══════════════════════════════════
Multiple widgets used the same GlobalKey.
════════════════════════════════════════════════════════════════════════════════
现有代码
模型代码
import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; class AddCourse with ChangeNotifier { String? sectionName; List<String>? sections; List<dynamic>? addVids; AddCourse({this.sectionName, this.sections, this.addVids}); Map<String, dynamic> toJson() { final Map<String, dynamic> sectionData = <String, dynamic>{}; sectionData['Section #'] = sections; sectionData['Section Name'] = sectionName; return sectionData; } Map<dynamic, dynamic> toJson2() { final Map<dynamic, dynamic> vidData = <dynamic, dynamic>{}; vidData['Videos #'] = addVids; return vidData; } }
表单代码
import 'package:flutter/material.dart'; import 'package:snippet_coder_utils/FormHelper.dart'; import '../provider/course_add_model.dart'; class CourseCardBody extends StatefulWidget { const CourseCardBody({Key? key}) : super(key: key); @override State<CourseCardBody> createState() => _CourseCardBodyState(); } class _CourseCardBodyState extends State<CourseCardBody> { GlobalKey<FormState> globalkey = GlobalKey(); AddCourse coursesModel = AddCourse(); @override void initState() { super.initState(); coursesModel.sections = List<String>.empty(growable: true); coursesModel.sections?.add(""); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Add Courses'), centerTitle: true, ), body: ListView.separated( shrinkWrap: true, physics: const ScrollPhysics(), itemBuilder: ((context, index) => Column( children: [ _uiWidget(index), Center( child: FormHelper.submitButton('Save', () { if (validateAndSave()) { print(coursesModel.toJson()); } }), ), ], )), separatorBuilder: ((context, index) => const Divider()), itemCount: coursesModel.sections!.length, ), ); } Widget _uiWidget(index) { return Form( key: globalkey, child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ _sectionsContainer(index), Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ Flexible( flex: 1, fit: FlexFit.loose, child: FormHelper.inputFieldWidgetWithLabel( context, 'Add Section$index', '', 'Section Title', (onValidate) { if (onValidate.isEmpty) { return 'section ${index + 1} name can\'t be empty'; } return null; }, (onSavedVal) { coursesModel.sections![index++] = index.toString(); onSavedVal = index; }, onChange: (onChangedval) { coursesModel.sectionName = onChangedval; }, initialValue: coursesModel.sectionName ?? "", borderColor: Colors.black, borderFocusColor: Colors.black, fontSize: 14, labelFontSize: 14, validationColor: Colors.redAccent, ), ), Visibility( visible: index == coursesModel.sections!.length - 1, child: IconButton( onPressed: () { addEmailControl(); }, icon: const Icon( Icons.add_circle, color: Colors.greenAccent, ), ), ), Visibility( visible: index > 0, child: SizedBox( width: 35, child: IconButton( onPressed: () { removeEmailControl(index); }, icon: const Icon( Icons.remove_circle, color: Colors.redAccent, ), ), ), ), ], ), ], ), ), ); } Widget _sectionsContainer(index) { return Column( children: [ Padding( padding: const EdgeInsets.all(10), child: Text( 'Section ${index + 1}', textAlign: TextAlign.left, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, ), ), ), ], ); } void addEmailControl() { setState(() { coursesModel.sections!.add(""); }); } void removeEmailControl(index) { setState(() { if (coursesModel.sections!.length > 1) { coursesModel.sections!.removeAt(index); } }); } bool validateAndSave() { if (globalkey.currentState!.validate()) { globalkey.currentState!.save(); return true; } else { return false; } } }
核心问题分析
- GlobalKey重复使用:每个动态生成的Section都包裹在独立
Form中,且所有Form共用同一个globalkey。GlobalKey在应用内必须唯一,重复绑定会直接触发错误。 - 模型逻辑混乱:
sectionName是单个字符串,无法存储多个Section的名称,应该直接用sections列表管理所有输入值。 - Save按钮重复渲染:每个Section下方都显示Save按钮,不符合需求。
- 输入回调逻辑错误:
onSaved中错误修改index值,导致无法正确对应当前Section的位置。
解决方法及修正代码
修正后的模型代码
import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; class AddCourse with ChangeNotifier { List<String>? sections; List<dynamic>? addVids; AddCourse({this.sections, this.addVids}); Map<String, dynamic> toJson() { final Map<String, dynamic> sectionData = <String, dynamic>{}; sectionData['Sections'] = sections; // 直接存储所有Section名称 return sectionData; } Map<dynamic, dynamic> toJson2() { final Map<dynamic, dynamic> vidData = <dynamic, dynamic>{}; vidData['Videos #'] = addVids; return vidData; } }
修正后的表单代码
import 'package:flutter/material.dart'; import 'package:snippet_coder_utils/FormHelper.dart'; import '../provider/course_add_model.dart'; class CourseCardBody extends StatefulWidget { const CourseCardBody({Key? key}) : super(key: key); @override State<CourseCardBody> createState() => _CourseCardBodyState(); } class _CourseCardBodyState extends State<CourseCardBody> { final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); final AddCourse _coursesModel = AddCourse(); @override void initState() { super.initState(); _coursesModel.sections = List<String>.empty(growable: true); _coursesModel.sections!.add(""); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Add Courses'), centerTitle: true, ), body: Form( key: _formKey, child: Column( children: [ Expanded( child: ListView.separated( shrinkWrap: true, physics: const ScrollPhysics(), itemBuilder: ((context, index) => _sectionWidget(index)), separatorBuilder: ((context, index) => const Divider()), itemCount: _coursesModel.sections!.length, ), ), // 仅显示一次的Save按钮 Padding( padding: const EdgeInsets.symmetric(vertical: 16.0), child: FormHelper.submitButton('Save', () { if (_validateAndSave()) { print(_coursesModel.toJson()); } }), ), ], ), ), ); } Widget _sectionWidget(int index) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _sectionsContainer(index), Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ Flexible( flex: 1, child: FormHelper.inputFieldWidgetWithLabel( context, 'section_$index', '', 'Section Title', (value) { if (value?.isEmpty ?? true) { return 'Section ${index + 1} name can\'t be empty'; } return null; }, (value) { // 正确保存当前Section的名称到列表对应位置 if (value != null) { _coursesModel.sections![index] = value; } }, onChange: (value) { // 实时更新列表值(可选) if (value != null) { setState(() { _coursesModel.sections![index] = value; }); } }, initialValue: _coursesModel.sections![index], borderColor: Colors.black, borderFocusColor: Colors.black, fontSize: 14, labelFontSize: 14, validationColor: Colors.redAccent, ), ), Visibility( visible: index == _coursesModel.sections!.length - 1, child: IconButton( onPressed: _addSection, icon: const Icon(Icons.add_circle, color: Colors.greenAccent), ), ), Visibility( visible: index > 0, child: SizedBox( width: 35, child: IconButton( onPressed: () => _removeSection(index), icon: const Icon(Icons.remove_circle, color: Colors.redAccent), ), ), ), ], ), ], ); } Widget _sectionsContainer(int index) { return Padding( padding: const EdgeInsets.all(10), child: Text( 'Section ${index + 1}', style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold), ), ); } void _addSection() { setState(() { _coursesModel.sections!.add(""); }); } void _removeSection(int index) { setState(() { if (_coursesModel.sections!.length > 1) { _coursesModel.sections!.removeAt(index); } }); } bool _validateAndSave() { if (_formKey.currentState!.validate()) { _formKey.currentState!.save(); return true; } return false; } }
关键修改说明
- 统一表单结构:整个页面用一个
Form包裹所有输入框,避免GlobalKey重复绑定。 - 简化模型逻辑:移除冗余的
sectionName字段,直接用sections列表存储所有Section名称。 - 优化按钮布局:Save按钮移到Form底部,仅渲染一次。
- 修正输入回调:确保
onSaved和onChange正确对应列表索引,保存实时输入值。
内容的提问来源于stack exchange,提问作者Sam Kh
相关产品推荐
相关产品推荐

