Search code examples
flutterformsproviderstate-managementconsumer

Why my Flutter forms can't hold their values?


I am using the following code to create a simple multi-steps question-answer form, but it seems forms can not hold their values and when I go to the second form and come back, the textFormField values are gone.

"Create.dart":

class Create with ChangeNotifier {
  String? title;
  String? description;

  int activeIndex = 0;
  int totalIndex = 6;

  changeStep(int index) {
    activeIndex = index;
    notifyListeners();
  }
}

"handler.dart":

class FormCreateHandler extends StatefulWidget {
  const FormCreateHandler({super.key});
  static const routeName = '/-form-handler';

  @override
  State<FormCreateHandler> createState() =>
      _FormCreateHandlerState();
}

class _FormCreateHandlerState extends State<FormCreateHandler> {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<Create>(
      create: (context) => Create(),
      child: Consumer<Create>(
        builder: (context, modal, child) {
          switch (modal.activeIndex) {
            case 0:
              return const FormQuestion1();
            case 1:
              return const FormQuestion2();
            default:
              return const FormQuestion1();
          }
        },
      ),
    );
  }
}

"form-question1.dart":

class FormQuestion1 extends StatefulWidget {
  const FormQuestion1({super.key});
  static const routeName = '/form-question1';

  @override
  State<FormQuestion1> createState() => _FormQuestion1State();
}

class _FormQuestion1State extends State<FormQuestion1> {
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) {
    return Consumer<Create>(builder: (context, modal, child) {
      return Form(
        key: _formKey,
        child: SingleChildScrollView(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              TextFormField(
                decoration: const InputDecoration(
                  labelText: 'title',
                  border: OutlineInputBorder(),
                ),
                validator: (value) {
                  if (value!.isEmpty)
                    return 'Please enter the title of question';
                },
              ),
              TextFormField(
                minLines: 3,
                maxLines: 5,
                decoration: const InputDecoration(
                  labelText: 'description',
                  border: OutlineInputBorder(),
                ),
                validator: (value) {
                  if (value!.isEmpty || value.length < 30) {
                    return 'Description must be longer';
                  }
                },
              ),
              Padding(
                padding: EdgeInsets.all(10),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Expanded(
                      flex: 1,
                      child: ElevatedButton(
                        onPressed: () {
                          if (_formKey.currentState?.validate() ?? false) {
                            _formKey.currentState!.save();
                            modal.changeStep(2);
                          }
                        },
                        child: Text('Back'),
                      ),
                    ),
                    Expanded(
                      flex: 2,
                      child: ElevatedButton(
                        onPressed: null, 
                        child: Text('Submit'),
                      ),
                    ),
                    Expanded(
                      flex: 1,
                      child: ElevatedButton(
                        onPressed: () {
                          if (_formKey.currentState?.validate() ?? false) {
                            _formKey.currentState!.save();
                            modal.changeStep(4);
                          }
                        },
                        child: Text('Next'),
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      );
    });
  }
}

"form-question2.dart"

class FormQuestion2 extends StatefulWidget {
  const FormQuestion2({super.key});
  static const routeName = '/form-question2';

  @override
  State<FormQuestion2> createState() => _FormQuestion2State();
}

class _FormQuestion2State extends State<FormQuestion2> {
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) {
    return Consumer<Create>(builder: (context, modal, child) {
      return Form(
        key: _formKey,
        child: SingleChildScrollView(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              TextFormField(
                decoration: const InputDecoration(
                  labelText: 'title',
                  border: OutlineInputBorder(),
                ),
                validator: (value) {
                  if (value!.isEmpty)
                    return 'Please enter the title of question';
                },
              ),
              TextFormField(
                minLines: 3,
                maxLines: 5,
                decoration: const InputDecoration(
                  labelText: 'description',
                  border: OutlineInputBorder(),
                ),
                validator: (value) {
                  if (value!.isEmpty || value.length < 30) {
                    return 'Description must be longer';
                  }
                },
              ),
              Padding(
                padding: EdgeInsets.all(10),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Expanded(
                      flex: 1,
                      child: ElevatedButton(
                        onPressed: () {
                          if (_formKey.currentState?.validate() ?? false) {
                            _formKey.currentState!.save();
                            modal.changeStep(2);
                          }
                        },
                        child: Text('Back'),
                      ),
                    ),
                    Expanded(
                      flex: 2,
                      child: ElevatedButton(
                        onPressed: null, 
                        child: Text('Submit'),
                      ),
                    ),
                    Expanded(
                      flex: 1,
                      child: ElevatedButton(
                        onPressed: () {
                          if (_formKey.currentState?.validate() ?? false) {
                            _formKey.currentState!.save();
                            modal.changeStep(4);
                          }
                        },
                        child: Text('Next'),
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      );
    });
  }
}

How can I modify this code to be able to hold the state of the forms until the user either presses the Submit button or closes the form?


Solution

  • Based on the other answer by @Aks, I came from a new idea to modify my question forms like below:

    "form-question1.dart":

    class FormQuestion1 extends StatefulWidget {
      const FormQuestion1({super.key});
      static const routeName = '/form-question1';
    
      @override
      State<FormQuestion1> createState() => _FormQuestion1State();
    }
    
    class _FormQuestion1State extends State<FormQuestion1> {
      final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
    
      @override
      Widget build(BuildContext context) {
        return Consumer<Create>(builder: (context, modal, child) {
          return Form(
            key: _formKey,
            child: SingleChildScrollView(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  TextFormField(
                    decoration: const InputDecoration(
                      labelText: 'title',
                      border: OutlineInputBorder(),
                    ),
                    validator: (value) {
                      if (value!.isEmpty)
                        return 'Please enter the title of question';
                    },
    
                initialValue:
                    Provider.of<Create>(context, listen: false).title,
    
                onSaved: (value) {
                  Provider.of<Create>(context, listen: false).title =
                      value;
                },
                  ),
                  TextFormField(
                    minLines: 3,
                    maxLines: 5,
                    decoration: const InputDecoration(
                      labelText: 'description',
                      border: OutlineInputBorder(),
                    ),
                    validator: (value) {
                      if (value!.isEmpty || value.length < 30) {
                        return 'Description must be longer';
                      }
                    },
    
                initialValue:
                    Provider.of<Create>(context, listen: false).title,
    
                onSaved: (value) {
                  Provider.of<Create>(context, listen: false).title =
                      value;
                },
                  ),
                  Padding(
                    padding: EdgeInsets.all(10),
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Expanded(
                          flex: 1,
                          child: ElevatedButton(
                            onPressed: () {
                              if (_formKey.currentState?.validate() ?? false) {
                                _formKey.currentState!.save();
                                modal.changeStep(2);
                              }
                            },
                            child: Text('Back'),
                          ),
                        ),
                        Expanded(
                          flex: 2,
                          child: ElevatedButton(
                            onPressed: null, 
                            child: Text('Submit'),
                          ),
                        ),
                        Expanded(
                          flex: 1,
                          child: ElevatedButton(
                            onPressed: () {
                              if (_formKey.currentState?.validate() ?? false) {
                                _formKey.currentState!.save();
                                modal.changeStep(4);
                              }
                            },
                            child: Text('Next'),
                          ),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            ),
          );
        });
      }
    }
    

    The modifications are adding following initialValue and onSaved() properties to each textFormField:

            initialValue:
                Provider.of<Create>(context, listen: false).title,
    
            onSaved: (value) {
              Provider.of<Create>(context, listen: false).title =
                  value;
            },
    

    But as I am not sure if this is the best way of doing that, this question remains open maybe someone would help me to improve the answer furthermore!