flutter stepper widget - validating fields in individual steps
Asked Answered
T

3

5

i am using stepper widget in order to collect info from user and validate it, i need to call an API at each step hence validate each field in a step at every continue button ... i am using form state and form widget but the issue is that it validates entire fields in all steps in stepper... how can i validate only individual step in a stepper? i went through the documentation in Stepper and State classes in stepper.dart but there is no supporting function there

following is the code

class SubmitPayment extends StatefulWidget {


 SubmitPayment({Key key, this.identifier, this.amount, this.onResendPressed})
      : super(key: key);

  final String identifier;
  final String amount;
  final VoidCallback onResendPressed;

  @override
  State<StatefulWidget> createState() {
    return _SubmitPaymentState();
  }
}

class _SubmitPaymentState extends State<SubmitPayment> {
  final GlobalKey<FormState> _formKeyOtp = GlobalKey<FormState>();
  final FocusNode _otpFocusNode = FocusNode();
  final TextEditingController _otpController = TextEditingController();
  bool _isOTPRequired = false;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(top: 8.0),
      child: Form(
          key: _formKeyOtp,
          child: Column(children: <Widget>[
            Center(
                child: Padding(
                    padding:
                        EdgeInsets.symmetric(horizontal: 16.0, vertical: 5.0),
                    child: Text(
                      Translations.of(context).helpLabelOTP,
                      style: TextStyle(
                          color: Theme.of(context).primaryColor,
                          fontStyle: FontStyle.italic),
                    ))),
            CustomTextField(
              icon: Icons.vpn_key,
              focusNode: _otpFocusNode,
              hintText: Translations.of(context).otp,
              labelText: Translations.of(context).otp,
              controller: _otpController,
              keyboardType: TextInputType.number,
              hasError: _isOTPRequired,
              validator: (String t) => _validateOTP(t),
              maxLength: AppConstants.otpLength,
              obscureText: true,
            ),
            Center(
                child: ButtonBar(
              mainAxisSize: MainAxisSize.max,
              alignment: MainAxisAlignment.center,
              children: <Widget>[
                RaisedButton(
                  child: Text(Translations.of(context).resendOtpButton),
                  color: Colors.white,
                  textColor: Theme.of(context).primaryColor,
                  onPressed: widget.onResendPressed,
                ),
                RaisedButton(
                  child: Text(
                    Translations.of(context).payButton,
                  ),
                  onPressed: _doPullPayment,
                ),
              ],
            )),
          ])),
    );
  }

  String _validateOTP(String value) {
    if (value.isEmpty || value.length < AppConstants.otpLength) {
      setState(() => _isOTPRequired = true);
      return Translations.of(context).invalidOtp;
    }
    return "";
  }

  bool _validateOtpForm() {
    _formKeyOtp.currentState.save();
    return this._formKeyOtp.currentState.validate();
  }

  Future<void> _doPullPayment() async {
    setState(() {
      _isOTPRequired = false;
    });

    if (!_validateOtpForm()) return false;

    try {
      setState(() {
        _isOTPRequired = false;
      });
      showDialog(
        barrierDismissible: false,
        context: context,
        builder: (context) => AlertDialog(
              content: ListTile(
                leading: CircularProgressIndicator(),
                title: Text(Translations.of(context).processingPaymentDialog),
              ),
            ),
      );

      TransactionApi api =
          TransactionApi(httpDataSource, authenticator.sessionToken);
      String responseMessage = await api.doPullPayment(
          widget.identifier,
          widget.amount,
          _otpController.text,
          TransactionConstants.transactionCurrency);

      Navigator.of(context).pop();
      await showAlertDialog(
          context, Translations.of(context).pullPayment, '$responseMessage');
      Navigator.pop(context);
    } catch (exception) {
      await showAlertDialog(context, Translations.of(context).pullPayment,
          '${exception.message}');
      Navigator.of(context).pop();
    }
  }
Timework answered 8/7, 2018 at 10:38 Comment(1)
also did not find if i can check validation in individual form fields in form.dartTimework
R
14

One approach is to use a separate Form for each step. To handle that, use a list of GlobalKey<FormState> which you can index based on _currentStep, then call validate() in onStepContinue:

List<GlobalKey<FormState>> _formKeys = [GlobalKey<FormState>(), GlobalKey<FormState>(), …];
…

Stepper(
  currentStep: _currentStep,
  onStepContinue: () {
    setState(() {
      if (_formKeys[_currentStep].currentState?.validate()) {
        _currentStep++;
      }
    });
  },
  steps: 
  Step(
    child: Form(key: _formKeys[0], child: …),

This implies the following:

  1. Since you're calling an API at the end, you need to check if you're validating the last step, and save instead of just validating;
  2. You probably want to factor our the Forms to several widgets. If you do so, do not confuse the key parameter that every Widget has. Pass the formKey as an unnamed parameter to avoid confusion.
Rectory answered 13/8, 2018 at 11:31 Comment(2)
i was doing that only , the problem was in validate function which was returning an empty string instead of a null which was causing an issue, please check my answer for detailTimework
As you deduced (below), you need to return null, not an empty string from the validate method. If you don't have an explicit return statement, this achieves the same end result.Rectory
T
1

So i solved this as follows:

The problem was that i was returning an *empty string ("") * if the my logic was valid, where as validate method of FormState expects each validator method, associated with TextFormField to return null if validation is passed.

i changed following

 String _validateOTP(String value) {
    if (value.isEmpty || value.length < AppConstants.otpLength) {
      setState(() => _isOTPRequired = true);
      return Translations.of(context).invalidOtp;
    }
    return "";
  }

to

  String _validateOTP(String value) {
if (value.isEmpty || value.length < AppConstants.otpLength) {
  setState(() => _isOTPRequired = true);
  return Translations.of(context).invalidOtp;
}
return null;

}

and it worked all fine then.

Refer to this link for details "If there is an error with the information the user has provided, the validator function must return a String containing an error message. If there are no errors, the function should not return anything."

Timework answered 28/8, 2018 at 18:31 Comment(3)
You can achieve the same end result by not having any return statement in the valid case (i.e. just remove the return null; part) :)Rectory
@DerekLakin that will cause ugly compiler warning : "warning: This function declares a return type of 'String', but doesn't end with a return statement. (missing_return at file.dart:166)"Timework
Sorry, yes. I was thinking of the inline case, not a separate function. To be honest, I think the compiler should give the same warning for inline, too :)Rectory
M
1

It's been long since this question was asked. I hope my answer can help. To do this, I created a List<GlobalKey> then in the onContinue of the Stepper I did something as

final List<GlobalKey<FormState>> _formKeys = [
    GlobalKey<FormState>(),
    GlobalKey<FormState>(),
    GlobalKey<FormState>(),
    GlobalKey<FormState>()
  ];                                                                 continued()  {
    if(_formKeys[_currentStep].currentState!.validate()) {
      switch(_currentStep){
      case 0:
        setSender();
        break;
      case 1:
        setReceiver();
        break;
    }
    }

}

Marquettamarquette answered 3/6, 2022 at 10:9 Comment(1)
What is the difference with the other answer?Accouchement

© 2022 - 2024 — McMap. All rights reserved.