0

I have 3 widgets.

The HomeApp - A stateless Widget that connects everything.
A custom AppBarWidget - A Stateful Widget which have a submit button.
A SurveyForm Widget - A Stateful widget which have 50+ inputs including TextFormField, radio buttons, and a whole bunch of custom input types.

When the user press the submit button in the appbar, it needs to do some custom validations in the survey form, create a new model class. which translate into a JSON object, and get POST to an API.

I've read a few options,

  1. Initiate all the variables and the validation/submit logics in the HomeApp, pass it down to the SurveyForm, and use callback to trigger the function. But it involves me passing down 50+ parameters to the SurveyForm.
  2. Using a GlobalKey. Not really sure how it works, When I try to do final key = new GlobalKey<SurveyForm>();, It said 'SurveyForm' doesn't extend 'State<StatefulWidget>'..
  3. Pass the parent instance to the child widget, suggested by this link.
  4. Calling a method of child widget, but this link said it's discouraged.
  5. Write a custom controller.

What would be the preferable way in Flutter?

hook38
  • 3,899
  • 4
  • 32
  • 52

2 Answers2

0

You can use a Form widget Tutorial. Basically you wrap all of your TextFields and checkbox and other stuff with a Form widget and set it's key to a GlobalKey<FormState>(). now you can use another type of fields in it(for example instead of TextaField you use a TextFormField) now you can set a property called onSaved on them(We're gonna use it in a bit). then whenever you want to submit the forms data, you call a function that you've written in your state class(by adding it to an onPressed value of a button). It's something like this:

void _submitForm() {
    final form = _formKey.currentState; // this is your GlobalKey<FormState> that you specified and set to the key of your Form Widget.
    if (form.validate()) { // form.validate() will call all the validator functions that you've passed to your inputfields
      form.save(); // This will call all the onSaved functions that you passed to your forms.
    }
}

It gets your Form widget, calls all the validator functions of the inputs, if everything fits together, calls all the onSaved functions of the inputs.

You can create a dictionary and in onSaved of the inputfields set the inputs values in it. Now you can use json.encode(your_data_dict) to turn it to a JSON string or use your object. then post the json.

Parsa
  • 103
  • 1
  • 7
  • How do you use Form widget on Radio buttons? I can't find any examples. – hook38 Sep 03 '20 at 02:46
  • Radio Buttons don't need to be validated, just try to manipulate your data dictionary on their onChanged callback. – Parsa Sep 03 '20 at 06:58
  • Don't need to be or can't be? I have a requirement to validate if a set of radio buttons were selected. I'm looking in wrapping the whole set in FormField widget, but I can't figure out how to trigger the "didChange" method in FormField. – hook38 Sep 03 '20 at 08:21
0

So I managed to solved this.
My problem should actual be break down into 3 problems.

  1. Validate textformfield, radio buttons, all custom inputs.
  2. Create a new model class that can be passed translate to JSON.
  3. Submit the form from a button within another widget (Appbar).

In order to solve the first one, I wrapped everything in SurveyForm into a Form widget. This part is simple, there are many tutorials online. In order to do validation on custom inputs, I wrapped the input in a FormField widget, and trigger the didChange method of FormFieldState. This allows me to use validator and onSaved method of FormField using methods callback. Below is my working code for a datepicker.

class Q1<T> extends StatefulWidget {
  final FormFieldValidator<DateTime> validator;
  final FormFieldSetter<DateTime> onSaved;

  const Q1({
    Key key,
    @required this.onSaved,
    @required this.validator,
  })  : assert(validator != null),
        assert(onSaved != null),
        super(
          key: key,
        );

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

class _Q1State extends State<Q1> {
  DateTime _completionDate;

  @override
  Widget build(BuildContext context) {
    return FormField(
      validator: (val) {
        return widget.validator(val);
      },
      onSaved: (val) {
        widget.onSaved(val);
      },
      builder: (FormFieldState<dynamic> field) {
        return Row(
          children: [
            Expanded(
              flex: 1,
              child: Text('1. Completion Date'),
            ),
            Expanded(
              flex: 2,
              child: Row(
                children: [
                  Container(
                    padding: EdgeInsets.all(
                      SizeConfig().getProportionateScreenWidth(
                          kDatePickerIconTextBoxPadding),
                    ),
                    decoration: BoxDecoration(
                      border: Border.all(
                        color: kDatePickerIconTextBoxBorderColor,
                      ),
                      borderRadius: BorderRadius.all(Radius.circular(
                        SizeConfig().getProportionateScreenWidth(
                            kDatePickerIconTextBoxRadius),
                      ) //         <--- border radius here
                          ),
                    ),
                    child: Text(_completionDate == null
                        ? 'Pick a date'
                        : DateFormat(kDateFormat).format(_completionDate)),
                  ),
                  IconButton(
                      onPressed: () {
                        showDatePicker(
                          context: context,
                          initialDate: DateTime.now(),
                          firstDate: DateTime(2000),
                          lastDate: DateTime(2100),
                        ).then((date) {
                          setState(() {
                            field.didChange(date);
                            _completionDate = date;
                          });
                        });
                      },
                      icon: FaIcon(FontAwesomeIcons.calendarAlt)),
                ],
              ),
            ),
          ],
        );
      },
    );
  }
}

And I just called the widget with

Q1(
              validator: (val) {
                if (val == null) {
                  return 'Completion Date is missing';
                }
                return null;
              },
              onSaved: (value) {
                setState(() {
                  widget.questionnaire.completion_date = DateFormat(kDateFormat).format(value);
                });
              },
            ),

In order to solve the 2nd one. I used json_serializable, where the object can also be passed around between HomeApp and SurveyForm widget, so I don't need to pass around 50+ variables.

And for the 3rd problem, I initiated final _formKey = GlobalKey<FormState>(); in the HomeApp, and passed it down to the SurveyForm widget, and use onPressed callback in the Appbar to trigger validation/submit in the SurveyForm.

Bloc will be used for submitting JSON and load animation, but that's beyond this.

I'm new to flutter (Been using this for a month), please let me know if anyone have a better solution.

hook38
  • 3,899
  • 4
  • 32
  • 52