How to validate form when submit in flutter with flutter_bloc?
Asked Answered
R

2

9

This is my change password screen. I was using flutter_bloc for implementing mvvc pattern. This page works fine with bloc. But what I am trying to achieve is validate form when submitting the form. As I was new to flutter I have no idea how to do this.

Change Password Event

abstract class ChangePasswordEvent extends Equatable {
  const ChangePasswordEvent();
}

class SubmitButtonPressed extends ChangePasswordEvent {
  final String oldPassword;
  final String newPassword;

  const SubmitButtonPressed({@required this.oldPassword, this.newPassword});

  @override
  List<Object> get props => [oldPassword, newPassword];
}

Change Password State

abstract class ChangePasswordState extends Equatable {
  const ChangePasswordState();

  @override
  List<Object> get props => [];
}

class ChangePasswordInitial extends ChangePasswordState {}

class ChangePasswordLoading extends ChangePasswordState {}

class ChangePasswordSuccess extends ChangePasswordState {}

class ChangePasswordFailure extends ChangePasswordState {
  final String error;

  const ChangePasswordFailure({@required this.error});

  @override
  List<Object> get props => [error];

  @override
  String toString() => 'ChangePasswordFailure { error: $error }';
}

Change Password Bloc

class ChangePasswordBloc
    extends Bloc<ChangePasswordEvent, ChangePasswordState> {
  final UserRepository userRepository;

  ChangePasswordBloc({
    @required this.userRepository,
  }) : assert(userRepository != null);

  @override
  ChangePasswordState get initialState => ChangePasswordInitial();

  @override
  Stream<ChangePasswordState> mapEventToState(
      ChangePasswordEvent event) async* {
    if (event is SubmitButtonPressed) {
      yield ChangePasswordLoading();

      try {
        final bool isPasswordChanged = await userRepository.changePassword(
          event.oldPassword,
          event.newPassword,
        );

        if (isPasswordChanged) {
          yield ChangePasswordSuccess();
        }
      } catch (error) {
        yield ChangePasswordFailure(error: error);
      }
    }
  }
}

Change Password Page

class ChangePasswordPage extends StatelessWidget {
  final UserRepository userRepository;

  ChangePasswordPage({Key key, @required this.userRepository})
      : assert(userRepository != null),
        super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Change Password'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(20.0),
        child: BlocProvider(
          create: (context) {
            return ChangePasswordBloc(
              userRepository: userRepository,
            );
          },
          child: ChangePasswordForm(),
        ),
      ),
    );
  }
}

Change Password Form

class ChangePasswordForm extends StatefulWidget {
  @override
  _ChangePasswordFormState createState() => _ChangePasswordFormState();
}

class _ChangePasswordFormState extends State<ChangePasswordForm> {
  final userRepository = UserRepository();
  final _formKey = GlobalKey<FormState>();

  final _oldPassController = TextEditingController();
  final _newPassController = TextEditingController();
  final _confirmPassController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    _onSubmitButtonPressed() {
      BlocProvider.of<ChangePasswordBloc>(context).add(
        SubmitButtonPressed(
          oldPassword: _oldPassController.text,
          newPassword: _newPassController.text,
        ),
      );
    }

    return BlocListener<ChangePasswordBloc, ChangePasswordState>(
      listener: (context, state) {
        if (state is ChangePasswordFailure) {
          Scaffold.of(context).showSnackBar(
            SnackBar(
              content: Text('${state.error}'),
              backgroundColor: Colors.red,
            ),
          );
        }

        if (state is ChangePasswordSuccess) {
          Scaffold.of(context).showSnackBar(
            SnackBar(
              content: Text('Password Changed Successfully'),
              backgroundColor: Colors.green,
            ),
          );
        }
      },
      child: BlocBuilder<ChangePasswordBloc, ChangePasswordState>(
        builder: (context, state) {
          return Form(
            key: _formKey,
            child: Column(
              children: [
                TextFormField(
                  decoration: InputDecoration(labelText: 'Old Password'),
                  controller: _oldPassController,
                ),
                SizedBox(height: 20.0),
                TextFormField(
                  decoration: InputDecoration(labelText: 'New Password'),
                  controller: _newPassController,
                  obscureText: true,
                ),
                SizedBox(height: 20.0),
                TextFormField(
                  decoration: InputDecoration(labelText: 'Confirm Password'),
                  controller: _confirmPassController,
                  obscureText: true,
                  validator: (value) {
                    final String _newPassword = _newPassController.text;

                    if (_newPassword != value) {
                      return "Password Mismatch";
                    }

                    return null;
                  },
                ),
                SizedBox(height: 20.0),
                RaisedButton(
                  onPressed: () {
                    if (state is! ChangePasswordLoading) {
                      final form = _formKey.currentState;

                      if (form.validate()) {
                        return _onSubmitButtonPressed();
                      }

                      return null;
                    }
                  },
                  child: Text('Submit'),
                ),
              ],
            ),
          );
        },
      ),
    );
  }
}
Rodriques answered 18/2, 2020 at 9:40 Comment(2)
You are validating it on submit _formKey.currentState.validate(). What would be your issue?Slipnoose
Checkout flutter_form_bloc, it have a bloc designed for forms, it will save you a lot of code in your case.Jit
C
12

You go and see on their example at: Form Validation

The example validate the email & password format, you should change it accordingly. Your state should be something like:

class MyFormState extends Equatable {
  final String email;
  final bool isEmailValid;
  final String password;
  final bool isPasswordValid;
  final bool formSubmittedSuccessfully;

  bool get isFormValid => isEmailValid && isPasswordValid;
  //....
}

The BLoC to validate:

class MyFormBloc extends Bloc<MyFormEvent, MyFormState> {
  final RegExp _emailRegExp = RegExp(
    r'^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$',
  );
  final RegExp _passwordRegExp = RegExp(
    r'^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$',
  );

  @override
  MyFormState get initialState => MyFormState.initial();

  @override
  void onTransition(Transition<MyFormEvent, MyFormState> transition) {
    print(transition);
  }

  @override
  Stream<MyFormState> mapEventToState(
    MyFormEvent event,
  ) async* {
    if (event is EmailChanged) {
      yield state.copyWith(
        email: event.email,
        isEmailValid: _isEmailValid(event.email),
      );
    }
    if (event is PasswordChanged) {
      yield state.copyWith(
        password: event.password,
        isPasswordValid: _isPasswordValid(event.password),
      );
    }
    if (event is FormSubmitted) {
      yield state.copyWith(formSubmittedSuccessfully: true);
    }
    if (event is FormReset) {
      yield MyFormState.initial();
    }
  }

  bool _isEmailValid(String email) {
    return _emailRegExp.hasMatch(email);
  }

  bool _isPasswordValid(String password) {
    return _passwordRegExp.hasMatch(password);
  }
}

And the the Form build method:

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<MyFormBloc, MyFormState>(
      builder: (context, state) {
        if (state.formSubmittedSuccessfully) {
          return SuccessDialog(onDismissed: () {
            _emailController.clear();
            _passwordController.clear();
            _myFormBloc.add(FormReset());
          });
        }
        return Form(
          child: Column(
            children: <Widget>[
              TextFormField(
                controller: _emailController,
                decoration: InputDecoration(
                  icon: Icon(Icons.email),
                  labelText: 'Email',
                ),
                keyboardType: TextInputType.emailAddress,
                autovalidate: true,
                validator: (_) {
                  return state.isEmailValid ? null : 'Invalid Email';
                },
              ),
              TextFormField(
                controller: _passwordController,
                decoration: InputDecoration(
                  icon: Icon(Icons.lock),
                  labelText: 'Password',
                ),
                obscureText: true,
                autovalidate: true,
                validator: (_) {
                  return state.isPasswordValid ? null : 'Invalid Password';
                },
              ),
              RaisedButton(
                onPressed: state.isFormValid ? _onSubmitPressed : null,
                child: Text('Submit'),
              ),
            ],
          ),
        );
      },
    );
  }
Collaborative answered 10/4, 2020 at 11:52 Comment(0)
W
1

I believe it is not best to use the flutter_bloc package for validation purposes as it will cause a rebuild for all controls. Instead use primitive objects like Stream and Provider as describe here. https://www.youtube.com/watch?v=JqWK4oitJFs

Additional link: https://medium.com/swlh/how-to-create-a-simple-login-form-in-flutter-using-bloc-pattern-b55ad52a2a10

Winsor answered 24/6, 2020 at 9:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.