title: Validation using GlobalKey<FormState> in individual steps of Flutter Stepper widget returns false even for correct input
body: <p>I am using stepper widget, and using a Form widget in each step of stepper with different form keys in order to validate each step ... the problem is that <code>this._formKey.currentState.validate()</code> always returns false even if the validation criteria fulfills for the current step ... following is the code that i use to build individual steps in stepper:</p>
  
  <pre><code>  Widget _buildCustomerInfoWidget() {
      return Form(
          key: _formKey,
          child: Column(
            children: &lt;Widget&gt;[
              CustomTextField(
                focusNode: _focusNodeID,
                hintText: Translations.of(context).cnic,
                labelText: Translations.of(context).cnic,
                controller: _controllerIdentifier,
                keyboardType: TextInputType.number,
                hasError: _isIdentifierRequired,
                validator: (String t) =&gt; _validateIdentifier(t),
                maxLength: 13,
              ),
              CustomTextField(
                focusNode: _focusNodeAmount,
                hintText: Translations.of(context).amount,
                labelText: Translations.of(context).amount,
                controller: _controllerAmount,
                keyboardType: TextInputType.number,
                hasError: _isAmountRequired,
                validator: (String t) =&gt; _validateAmount(t),
                maxLength: 6,
              ),
            ],
          ));
    }
  
    Widget _buildCustomerVerificationWidget() {
      return Form(
          key: _formKeyOTP,
          child: Column(
            children: &lt;Widget&gt;[
              Center(
                  child: Padding(
                      padding:
                          EdgeInsets.symmetric(horizontal: 16.0, vertical: 5.0),
                      child: Text(
                        Translations.of(context).helpLabelOTP,
                        style: new TextStyle(
                            color: Theme.of(context).primaryColor,
                            fontStyle: FontStyle.italic),
                      ))),
              CustomTextField(
                focusNode: _focusNodeOTP,
                hintText: Translations.of(context).otp,
                labelText: Translations.of(context).otp,
                controller: _controllerOTP,
                keyboardType: TextInputType.number,
                hasError: _isAmountRequired,
                validator: (String t) =&gt; _validateOTP(t),
                maxLength: 4,
                obscureText: true,
              ),
            ],
          ));
    }
  
    List&lt;Step&gt; _buildStepperSteps(BuildContext context) {
      List&lt;Step&gt; paymentSteps = [
        Step(
            title: Text(
              Translations.of(context).infoStepLabel,
            ),
            content: _buildCustomerInfoWidget(),
            state: StepState.indexed,
            isActive: _isStepActive),
        Step(
            title: Text(Translations.of(context).submitStepLabel),
            state: StepState.indexed,
            content: _buildCustomerVerificationWidget(),
            isActive: !_isStepActive),
      ];
  
      return paymentSteps;
    }
  </code></pre>
  
  <p>Complete code for reference:</p>
  
  <pre><code>class PullPaymentPage extends StatefulWidget {
    PullPaymentPage({Key key, this.title}) : super(key: key);
  
    final String title;
  
    @override
    State&lt;StatefulWidget&gt; createState() {
      return PullPaymentState();
    }
  }
  
  class PullPaymentState extends State&lt;PullPaymentPage&gt; {
    final GlobalKey&lt;FormState&gt; _formKey = new GlobalKey&lt;FormState&gt;();
    final GlobalKey&lt;FormState&gt; _formKeyOTP = new GlobalKey&lt;FormState&gt;();
    final TextEditingController _controllerIdentifier =
        new TextEditingController();
    final FocusNode _focusNodeOTP = new FocusNode();
    final FocusNode _focusNodeID = new FocusNode();
    final FocusNode _focusNodeAmount = new FocusNode();
    final TextEditingController _controllerAmount = new TextEditingController();
    final TextEditingController _controllerOTP = new TextEditingController();
  
    bool _isIdentifierRequired = false;
    bool _isAmountRequired = false;
    bool _isOTPRequired = false;
    bool _isStepActive = true;
    int _currentStep = 0;
  
  
    @override
    Widget build(BuildContext context) {
      return Scaffold(
          appBar: _buildAppBar(context),
          body: new Builder(builder: (BuildContext context) {
            return Container(child: _buildStepper(context));
          }));
    }
  
    Stepper _buildStepper(BuildContext context) {
      final Map functionMap = {0: _sendOTP, 1: _pullPayment};
  
      List&lt;Step&gt; paymentSteps = _buildStepperSteps(context);
  
      return Stepper(
        steps: paymentSteps,
        type: StepperType.vertical,
        currentStep: this._currentStep,
        onStepTapped: (step) {
          setState(() {
            if (step != 1) {
              _currentStep = step;
            }
            (_currentStep == 0) ? _isStepActive = true : _isStepActive = false;
          });
        },
        onStepContinue: () {
          functionMap[_currentStep]().then((value) {
            if (value == true) {
              setState(() {
                _currentStep++;
              });
              if (_currentStep == 1) {
                Scaffold.of(context).showSnackBar(SnackBar(
                      content: Text(Translations.of(context).otpSuccess),
                      duration: Duration(seconds: 4),
                    ));
              }
            }
          });
        },
        onStepCancel: () {
          setState(() {
            _currentStep == 0 ? Navigator.of(context).pop() : stepBack();
          });
        },
      );
    }
  
    void stepBack() {
      _isStepActive = true;
      _currentStep--;
    }
  
    Map&lt;int, Function&gt; createFunctionMap() {
      Map functionMap = {1: _sendOTP(), 2: _pullPayment()};
      return functionMap;
    }
  
    AppBar _buildAppBar(BuildContext context) {
      return AppBar(
        title: Text(
          Translations.of(context).pullPayment,
        ),
        backgroundColor: Theme.of(context).primaryColorDark,
        leading: new BackButton(),
      );
    }
  
    Widget _buildCustomerInfoWidget() {
      return Form(
          key: _formKey,
          child: Column(
            children: &lt;Widget&gt;[
              CustomTextField(
                focusNode: _focusNodeID,
                hintText: Translations.of(context).cnic,
                labelText: Translations.of(context).cnic,
                controller: _controllerIdentifier,
                keyboardType: TextInputType.number,
                hasError: _isIdentifierRequired,
                validator: (String t) =&gt; _validateIdentifier(t),
                maxLength: 13,
              ),
              CustomTextField(
                focusNode: _focusNodeAmount,
                hintText: Translations.of(context).amount,
                labelText: Translations.of(context).amount,
                controller: _controllerAmount,
                keyboardType: TextInputType.number,
                hasError: _isAmountRequired,
                validator: (String t) =&gt; _validateAmount(t),
                maxLength: 6,
              ),
            ],
          ));
    }
  
    Widget _buildCustomerVerificationWidget() {
      return Form(
          key: _formKeyOTP,
          child: Column(
            children: &lt;Widget&gt;[
              Center(
                  child: Padding(
                      padding:
                          EdgeInsets.symmetric(horizontal: 16.0, vertical: 5.0),
                      child: Text(
                        Translations.of(context).helpLabelOTP,
                        style: new TextStyle(
                            color: Theme.of(context).primaryColor,
                            fontStyle: FontStyle.italic),
                      ))),
              CustomTextField(
                focusNode: _focusNodeOTP,
                hintText: Translations.of(context).otp,
                labelText: Translations.of(context).otp,
                controller: _controllerOTP,
                keyboardType: TextInputType.number,
                hasError: _isAmountRequired,
                validator: (String t) =&gt; _validateOTP(t),
                maxLength: 4,
                obscureText: true,
              ),
            ],
          ));
    }
  
    List&lt;Step&gt; _buildStepperSteps(BuildContext context) {
      List&lt;Step&gt; paymentSteps = [
        Step(
            title: Text(
              Translations.of(context).infoStepLabel,
            ),
            content: _buildCustomerInfoWidget(),
            state: StepState.indexed,
            isActive: _isStepActive),
        Step(
            title: Text(Translations.of(context).submitStepLabel),do
            state: StepState.indexed,
            content: _buildCustomerVerificationWidget(),
            isActive: !_isStepActive),
      ];
  
      return paymentSteps;
    }
  
    String _validateIdentifier(String value) {
      if (value.isEmpty || value.length &lt; 11 || value.length &gt; 13) {
        setState(() =&gt; _isIdentifierRequired = true);
        return Translations.of(context).invalidInput;
      }
      return "";
    }
  
    String _validateAmount(String value) {
      if (value.isEmpty) {
        setState(() =&gt; _isAmountRequired = true);
        return Translations.of(context).amountRequiredError;
      } else if (!RegexHelpers.amountValidatorRegex.hasMatch(value)) {
        return Translations.of(context).validAmountError;
      }
      return "";
    }
  
    String _validateOTP(String value) {
      if (value.isEmpty || value.length &lt; 4) {
        setState(() =&gt; _isOTPRequired = true);
        return Translations.of(context).invalidOtp;
      }
      return "";
    }
  
    bool _validateInfoForm() {
      _isOTPRequired = false;
      _isAmountRequired = false;
      _isIdentifierRequired = false;
  
      if (!this._formKey.currentState.validate()) {
        return false;
      }
      _formKey.currentState.save();
      return true;
    }
  
    bool _validateOtpForm() {
      _isOTPRequired = false;
      _isAmountRequired = false;
      _isIdentifierRequired = false;
  
      if (!this._formKeyOTP.currentState.validate()) {
        return false;
      }
      _formKeyOTP.currentState.save();
      return true;
    }
  
    Future&lt;bool&gt; _sendOTP() async {
      bool sendOTP = false;
      setState(() {
        _isIdentifierRequired = false;
        _isAmountRequired = false;
        _isOTPRequired = false;
      });
  
      if (!_validateInfoForm()) {
        setState(() {
          _validateAmount(_controllerAmount.text);
          _validateIdentifier(_controllerIdentifier.text);
        });
  
        if (_validateAmount(_controllerAmount.text).isNotEmpty ||
            _validateIdentifier(_controllerIdentifier.text).isNotEmpty) {
          return false;
        }
      }
      try {
        showDialog(
          barrierDismissible: false,
          context: context,
          builder: (context) =&gt; AlertDialog(
                content: ListTile(
                  leading: CircularProgressIndicator(),
                  title: Text(Translations.of(context).processingSendOtpDialog),
                ),
              ),
        );
  
        TransactionApi api =
            new TransactionApi(httpDataSource, authenticator.sessionToken);
        sendOTP = await api.sendOTP(_controllerIdentifier.text);
  
        if (sendOTP) {
          _isStepActive = false;
          _controllerOTP.clear();
        }
  
        Navigator.of(context).pop();
      } catch (exception) {
        await showAlertDialog(context, Translations.of(context).pullPayment,
            '${exception.message}');
        Navigator.of(context).pop();
        return false;
      }
  
      return sendOTP;
    }
  
    Future&lt;bool&gt; _pullPayment() async {
      Result pullPaymentResponse = new Result();
      setState(() {
        _isIdentifierRequired = false;
        _isAmountRequired = false;
        _isOTPRequired = false;
      });
  
      if (!_validateOtpForm()) {
        setState(() {
          _validateOTP(_controllerAmount.text);
        });
  
        if (_validateAmount(_controllerAmount.text).isNotEmpty ||
            _validateIdentifier(_controllerIdentifier.text).isNotEmpty ||
            _validateOTP(_controllerOTP.text).isNotEmpty) {
          return false;
        }
      }
  
      try {
        setState(() {
          _isOTPRequired = false;
        });
        showDialog(
          barrierDismissible: false,
          context: context,
          builder: (context) =&gt; AlertDialog(
                content: ListTile(
                  leading: CircularProgressIndicator(),
                  title: Text(Translations.of(context).processingPaymentDialog),
                ),
              ),
        );
  
        TransactionApi api =
            new TransactionApi(httpDataSource, authenticator.sessionToken);
        pullPaymentResponse = await api.pullPayment(_controllerIdentifier.text,
            _controllerAmount.text, _controllerOTP.text);
  
        Navigator.of(context).pop();
      } catch (exception) {
        await showAlertDialog(context, Translations.of(context).pullPayment,
            '${exception.message}');
        Navigator.of(context).pop();
        return false;
      }
  
      if (successResponseCodes.contains(pullPaymentResponse.responseCode)) {
        await showAlertDialog(context, Translations.of(context).pullPayment,
            '${pullPaymentResponse.description}');
        Navigator.pop(context);
        return true;
      }
      return false;
    }
  }
  </code></pre>