flutterdarttexteditingcontroller

Created 2 text fields with a loop. Now they have same value


Im trying to create a trainings app for myself. I have created a app that can add more textfields everytime a button is pushed. The problem im having is all the fields have the same value and i cant enter two diffrent

I have tried a couple of things and the closest i got was when i could do diffrent values but not save it.

You can see the picture of the problem here

My code is the following:

class EventEditingPage extends StatefulWidget {
  final Event? event;

  const EventEditingPage({Key? key, this.event}) : super(key: key);

  @override
  _EventEditingPageState createState() => _EventEditingPageState();
}

class _EventEditingPageState extends State<EventEditingPage> {
  final _formKey = GlobalKey<FormState>();
  final setsController = TextEditingController();
  final repsController = TextEditingController();
  final loadController = TextEditingController();
  late DropdownButton Exercise; 

  @override
  void initState() {
    super.initState();
      final event = widget.event!;

      exercise = event.Exercise;
      setsController.text = event.Sets;
      repsController.text = event.Reps;
      loadController.text = event.Load;

    }
  }

  @override
  void dispose() {
    setsController.dispose();   
    repsController.dispose();
    loadController.dispose();
    super.dispose();
  }
  
int numberOfTextFields = 1;
  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(
          leading: CloseButton(),
          actions: buildEditingActions(),
          backgroundColor: Colors.red,
        ),
        body: SingleChildScrollView(
          padding: EdgeInsets.all(12),
          child: Form(
            key: _formKey,
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                Text("text", style: TextStyle(fontSize: 24)),
                SizedBox(height: 12),
                Text("\ntext", style: TextStyle(fontSize: 24)),
                for(int i = 0 ; i < numberOfTextFields ; i++) 
                buildExerciseComplete(),
                buildAddMore(),
              ],
            ),
          ),
        ),
      );

  List<Widget> buildEditingActions() => [
        ElevatedButton.icon(
          style: ElevatedButton.styleFrom(
              primary: Colors.transparent, shadowColor: Colors.transparent),
          onPressed: saveForm,
          icon: Icon(Icons.done),
          label: Text('Save'),
        )
      ];


  Widget buildExerciseComplete() => Container(
        padding: EdgeInsets.all(5.0),
        decoration: BoxDecoration(
          color: Color.fromARGB(255, 255, 200, 200),
          borderRadius: BorderRadius.circular(5.0),
          border: Border.all(
            width: 2.0,
          ),
        ),
        height: 50,
        child: Row(
          children: <Widget>[
            Expanded(
              child: Row(
                children: <Widget>[
                  Expanded(child: buildExercise()),
                  Expanded(child: buildSets()),
                  Expanded(child: buildReps()),
                  Expanded(child: buildLoad()),
                ],
              ),
            ),
          ],
        ),
      );

  Widget buildSets() => TextFormField(
        keyboardType: TextInputType.number,
        inputFormatters: <TextInputFormatter>[
          FilteringTextInputFormatter.digitsOnly
        ],
        style: TextStyle(fontSize: 15),
        decoration: InputDecoration(
          border: OutlineInputBorder(),
          labelText: 'Sets',
        ),
        onFieldSubmitted: (_) => saveForm,
        validator: (sets) =>
            sets != null && sets.isEmpty ? 'Sets cannot be empty' : null,
             controller: setsController,
      );

  Widget buildReps() => TextFormField(
        keyboardType: TextInputType.number,
        inputFormatters: <TextInputFormatter>[
          FilteringTextInputFormatter.digitsOnly
        ],
        style: TextStyle(fontSize: 15),
        decoration: InputDecoration(
          border: OutlineInputBorder(),
          labelText: 'Reps',
        ),
        onFieldSubmitted: (_) => saveForm,
        validator: (reps) =>
            reps != null && reps.isEmpty ? 'Reps cannot be empty' : null,
        controller: repsController,
      );

  Widget buildLoad() => TextFormField(
        keyboardType: TextInputType.number,
        inputFormatters: <TextInputFormatter>[
          FilteringTextInputFormatter.digitsOnly
        ],
        style: TextStyle(fontSize: 15),
        decoration: InputDecoration(
          labelText: 'Load',
          border: OutlineInputBorder(),
        ),
        onFieldSubmitted: (_) => saveForm,
        validator: (load) =>
            load != null && load.isEmpty ? 'Load cannot be empty' : null,
        controller: loadController,
      );

  String exercise = 'Squat';
  Widget buildExercise() => DropdownButton<String>(
        value: exercise,
        icon: const Icon(Icons.arrow_drop_down),
        elevation: 16,
        style: const TextStyle(color: Colors.black),
        onChanged: (String? newValue) {
          setState(() {
            exercise = newValue!;
          });
        },
        items: <String>['Squat', 'Deadlift', 'Bench']
            .map<DropdownMenuItem<String>>((String value) {
          return DropdownMenuItem<String>(
            value: value,
            child: Text(value),
          );
        }).toList(),
      );

  Widget buildAddMore() => ElevatedButton(
            onPressed: () {
              setState((){
        numberOfTextFields++;
        _textEditingControllers.add(TextEditingController());
    });
            },
            child: Text('Add new exercise'),  
          );
        
  
  Future saveForm() async {
    final isValid = _formKey.currentState!.validate();

    if (isValid) {
      final event = Event(
          Exercise: exercise,
          Sets: setsController.text,
          Reps: repsController.text,
          Load: loadController.text);

      final isEditing = widget.event != null;
      final provider = Provider.of<EventProvider>(context, listen: false);
      if (isEditing) {
        provider.editEvent(event, widget.event!);

        Navigator.of(context).pop();
      } else {
        provider.addEvent(event);
      }

      Navigator.of(context).pop();
    }
  }
}

class Event {
  final Exercise;
  final String Sets;
  final String Reps;
  final String Load;
  final Color backgroundColor;


  const Event({
     required this.Sets,
    required this.Reps,
    required this.Load,
    this.backgroundColor = Colors.lightGreen,
  });
}

Im pretty the reason has something to do with TextEditingController()

Im hoping that somebody could help me with the solution to my problem :-)


Solution

  • It seems you are building all of the fields with the same controller, so all will have the same value. You should make a list of controllers and add controllers.

    Declare variables like this:

      final List<TextEditingController> setsController = [];
      final List<TextEditingController> repsController = [];
      final List<TextEditingController> loadController = [];
    

    Then Create Controller:

    createControllers() {
        for (var i = 0; i < numberOfTextFields; i++) {
          setsController.add(TextEditingController());
          repsController.add(TextEditingController());
          loadController.add(TextEditingController());
        }
      }
    

    Use the controller in respective text Field:

    controller: repsController[index]
    

    Call the create controller method at initState(). enter image description here

    I guess you can handle the value retrieve and dispose of the controllers from here.

    I don't know your save logic, so I commented it out.

    class _EventEditingPageState extends State<EventEditingPage> {
      final _formKey = GlobalKey<FormState>();
      final List<TextEditingController> setsController = [];
      final List<TextEditingController> repsController = [];
      final List<TextEditingController> loadController = [];
      // late DropdownButton Exercise;
      List<String> exceriseItems = ['Squat', 'Deadlift', 'Bench'];
      List<String> selectedExercise = [];
      int numberOfTextFields = 1;
    
      @override
      void initState() {
        super.initState();
        final event = widget.event!;
        createControllers();
        selectedExercise[0] = event.Exercise != "" ? event.Exercise : exceriseItems[0];
        setsController[0].text = event.Sets;
        repsController[0].text = event.Reps;
        loadController[0].text = event.Load;
      }
    
      @override
      void dispose() {
        setsController.forEach((TextEditingController element) {
          element.dispose();
        });
        // Or simply clear the array.
    
        // todo : repsController dispose();
        // todo : loadController dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) => Scaffold(
            appBar: AppBar(
              leading: const CloseButton(),
              actions: buildEditingActions(),
              backgroundColor: Colors.red,
            ),
            body: SingleChildScrollView(
              padding: const EdgeInsets.all(12),
              child: Form(
                key: _formKey,
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: <Widget>[
                    const Text("text", style: TextStyle(fontSize: 24)),
                    const SizedBox(height: 12),
                    const Text("\ntext", style: TextStyle(fontSize: 24)),
                    for (int i = 0; i < numberOfTextFields; i++)
                      buildExerciseComplete(i),
                    buildAddMore(),
                  ],
                ),
              ),
            ),
          );
    
      List<Widget> buildEditingActions() => [
            ElevatedButton.icon(
              style: ElevatedButton.styleFrom(
                  primary: Colors.transparent, shadowColor: Colors.transparent),
              onPressed: saveForm,
              icon: const Icon(Icons.done),
              label: const Text('Save'),
            )
          ];
    
      Widget buildExerciseComplete(int idx) => Container(
            padding: const EdgeInsets.all(5.0),
            decoration: BoxDecoration(
              color: const Color.fromARGB(255, 255, 200, 200),
              borderRadius: BorderRadius.circular(5.0),
              border: Border.all(
                width: 2.0,
              ),
            ),
            height: 50,
            child: Row(
              children: <Widget>[
                Expanded(
                  child: Row(
                    children: <Widget>[
                      Expanded(child: buildExercise(idx)),
                      Expanded(child: buildSets(idx)),
                      Expanded(child: buildReps(idx)),
                      Expanded(child: buildLoad(idx)),
                    ],
                  ),
                ),
              ],
            ),
          );
    
      Widget buildSets(int idx) => TextFormField(
            keyboardType: TextInputType.number,
            inputFormatters: <TextInputFormatter>[
              FilteringTextInputFormatter.digitsOnly
            ],
            style: const TextStyle(fontSize: 15),
            decoration: const InputDecoration(
              border: OutlineInputBorder(),
              labelText: 'Sets',
            ),
            onFieldSubmitted: (_) => saveForm,
            validator: (sets) =>
                sets != null && sets.isEmpty ? 'Sets cannot be empty' : null,
            controller: setsController[idx],
          );
    
      Widget buildReps(int idx) => TextFormField(
            keyboardType: TextInputType.number,
            inputFormatters: <TextInputFormatter>[
              FilteringTextInputFormatter.digitsOnly
            ],
            style: const TextStyle(fontSize: 15),
            decoration: const InputDecoration(
              border: OutlineInputBorder(),
              labelText: 'Reps',
            ),
            onFieldSubmitted: (_) => saveForm,
            validator: (reps) =>
                reps != null && reps.isEmpty ? 'Reps cannot be empty' : null,
            controller: repsController[idx],
          );
    
      Widget buildLoad(int idx) => TextFormField(
            keyboardType: TextInputType.number,
            inputFormatters: <TextInputFormatter>[
              FilteringTextInputFormatter.digitsOnly
            ],
            style: const TextStyle(fontSize: 15),
            decoration: const InputDecoration(
              labelText: 'Load',
              border: const OutlineInputBorder(),
            ),
            onFieldSubmitted: (_) => saveForm,
            validator: (load) =>
                load != null && load.isEmpty ? 'Load cannot be empty' : null,
            controller: loadController[idx],
          );
    
      Widget buildExercise(int idx) => DropdownButton<String>(
            value: selectedExercise[idx],
            icon: const Icon(Icons.arrow_drop_down),
            elevation: 16,
            style: const TextStyle(color: Colors.black),
            onChanged: (String? newValue) {
              setState(() {
                selectedExercise[idx] = newValue!;
              });
            },
            items: exceriseItems
                .map<DropdownMenuItem<String>>((String value) {
              return DropdownMenuItem<String>(
                value: value,
                child: Text(value),
              );
            }).toList(),
          );
    
      Widget buildAddMore() => ElevatedButton(
            onPressed: () {
              setState(() {
                numberOfTextFields++;
                createControllers();
              });
            },
            child: const Text('Add new exercise'),
          );
    
      createControllers() {
        for (var i = 0; i < numberOfTextFields; i++) {
          setsController.add(TextEditingController());
          repsController.add(TextEditingController());
          loadController.add(TextEditingController());
        }
        selectedExercise.add(exceriseItems[0]);
      }
    
      Future saveForm() async {
        final isValid = _formKey.currentState!.validate();
    
        if (isValid) {
          // final event = Event(
          //     Exercise: exercise,
          //     Sets: setsController.text,
          //     Reps: repsController.text,
          //     Load: loadController.text);
    
          final isEditing = widget.event != null;
          // final provider = Provider.of<EventProvider>(context, listen: false);
          // if (isEditing) {
          //   provider.editEvent(event, widget.event!);
          //
          //   Navigator.of(context).pop();
          // } else {
          //   provider.addEvent(event);
          // }
          //
          // Navigator.of(context).pop();
        }
      }
    }