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

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;
    }
  }
}

核心问题分析

  1. GlobalKey重复使用:每个动态生成的Section都包裹在独立Form中,且所有Form共用同一个globalkey。GlobalKey在应用内必须唯一,重复绑定会直接触发错误。
  2. 模型逻辑混乱:sectionName是单个字符串,无法存储多个Section的名称,应该直接用sections列表管理所有输入值。
  3. Save按钮重复渲染:每个Section下方都显示Save按钮,不符合需求。
  4. 输入回调逻辑错误: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;
  }
}

关键修改说明

  1. 统一表单结构:整个页面用一个Form包裹所有输入框,避免GlobalKey重复绑定。
  2. 简化模型逻辑:移除冗余的sectionName字段,直接用sections列表存储所有Section名称。
  3. 优化按钮布局:Save按钮移到Form底部,仅渲染一次。
  4. 修正输入回调:确保onSaved和onChange正确对应列表索引,保存实时输入值。

内容的提问来源于stack exchange,提问作者Sam Kh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 05:50:45