skip to Main Content

So I’m relatively new to flutter and I’ve been trying to dynamically add Sections(TextFormFields) that are represented in a form that has Form.Helper as its child and in the process to get the saveAndValidate method to work i had to use a GlobalKey to be able to access the currentState of its so i can validate and save user input and such, but whenever i try add another Section to the screen it display this error massage

════════ Exception caught by widgets library ═══════════════════════════════════
Multiple widgets used the same GlobalKey.
════════════════════════════════════════════════════════════════════════════════

here is the code I wrote and I’d appreciate any help in solving this error please.

#1- the code for the model I used:

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});
/*where we save our values later to push them to firbase/database*/

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> sectionData = <String, dynamic>{};
    sectionData['Section #'] =
        sections; // where current section number is saved and is stored dynamicly and updates as user adds more or less sections.
    sectionData['Section Name'] =
        sectionName; // where the input of the textformfield is saved and to be later pushed to the database and also is stored in a list so it can hold multiple section names as such.
    return sectionData;
  }

/* this is another model data for a functionality thats not implemented yet*/

  Map<dynamic, dynamic> toJson2() {
    final Map<dynamic, dynamic> vidData = <dynamic, dynamic>{};
    vidData['Videos #'] = addVids;
    return vidData;
  }
}

#2 this the code for the form I created

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> {
  /* this is where i set up my global key that has the type of GlobalKey<FormState>*/
  /*State associated with a [Form] widget. such as textformfields/forms/textfields..etc// the use of the (FormState) is to be able to  Access the Functions "save"/"validate"/"reset" as to use them with forms/textformfields that you want to validate thier input or save it*/
  GlobalKey<FormState> globalkey = GlobalKey();
  AddCourse coursesModel = AddCourse();
  @override
  void initState() {
    super.initState();
    coursesModel.sections = List<String>.empty(growable: true);
    coursesModel.sections?.add("");
    // adds empty sections to the list of sections when the add button is used
  }

  @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(
                  // the submit button here needs some work to only be show once but for now sorry for this annoying button.
                  child: FormHelper.submitButton('Save', () {
                    if (validateAndSave()) {
                      print(coursesModel.toJson());
                    }
                  }),
                ),
              ],
            )),
        separatorBuilder: ((context, index) => const Divider()),
        itemCount: coursesModel.sections!.length,
      ),
    );
  }

  Widget _uiWidget(index) {
/* this form here is the parent of form fields/Formhelper widgets as seen below*/
    return Form(
      /* -- note here--
       if we use a UniqueKey() 
      instead of our globalkey
       here and comment the ValidateAndSave() function here
       the form will work in terms of adding and removing sections 
       but we won't be able to either
        save content/input of the user in the fields or
         either validate
        them so that sucks. */

      /*this form is where global key is first used*/

      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  cant 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) {
    /* the widget used to create the current section displayed on the top left of each textformfields*/
    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() {
    /* we're especially using the <FormState> that is provided by the Globalkey to be able access the currentState of widget/form that has the global key in order to either validate or save the textformfields input or both in the same time*/

    // validate each form
    if (globalkey.currentState!.validate()) {
      //    If all data are correct then save data to out variables
      // save each form
      globalkey.currentState!.save();
      return true;
    } else {
      return false;
    }
  }
}

I’m trying my best to figure it out on my own as I want to know how to solve this problem properly and where did I go wrong, and any help is very much appreciated thank you!

2

Answers


  1. You may create a file of Global variables that may be shared across multiple files to ensure you are using a single instance.

    Example globals.dart file

    GlobalKey<SomeState> myGlobalKey = GlobalKey<SomeState>();
    

    Example of implementation inside main.dart (or whatever file)

    import './[path-to-globals]/globals.dart' // enter the appropriate path for your project
    
    ... // some code
    
    Form(
       key: myGlobalKey,
       ... // code
    )
    
    ... // maybe more code
    
    Login or Signup to reply.
  2. I suggest to create List<GlobalKey> variable. When you dynamically add or delete sub forms, you add or remove list items accordingly. It is impossible to use same GlobalKey for multiple widgets. So you need to create separate GlobalKeys for each form.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search