Last active
March 17, 2023 14:31
-
-
Save tayormi/d3b8cb6fd8710810268cf57c068f60c0 to your computer and use it in GitHub Desktop.
Riverpod Field Validation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'package:formz/formz.dart'; | |
| enum EmailValidationError { invalid } | |
| class Email extends FormzInput<String, EmailValidationError> { | |
| Email.pure([super.value = '']) : super.pure(); | |
| Email.dirty([super.value = '']) : super.dirty(); | |
| static final _emailRegExp = RegExp( | |
| r'^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$', | |
| ); | |
| late final validationResultCache = | |
| _emailRegExp.hasMatch(value) ? null : EmailValidationError.invalid; | |
| @override | |
| EmailValidationError? validator(String value) { | |
| return validationResultCache; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'package:formz/formz.dart'; | |
| enum NameValidationError { empty, invalid } | |
| class Name extends FormzInput<String, NameValidationError> { | |
| const Name.pure() : super.pure(''); | |
| const Name.dirty([String value = '']) : super.dirty(value); | |
| @override | |
| NameValidationError? validator(String value) { | |
| if (value.isEmpty) { | |
| return NameValidationError.empty; | |
| } else if (value.length < 3) { | |
| return NameValidationError.invalid; | |
| } else { | |
| return null; | |
| } | |
| } | |
| static String? showNameErrorMessage(NameValidationError? error) { | |
| if (error == NameValidationError.empty) { | |
| return 'Empty name'; | |
| } else if (error == NameValidationError.invalid) { | |
| return 'Too short name'; | |
| } else { | |
| return null; | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'package:formz/formz.dart'; | |
| enum PasswordValidationError { empty, invalid } | |
| class Password extends FormzInput<String, PasswordValidationError> { | |
| const Password.pure() : super.pure(''); | |
| const Password.dirty([String value = '']) : super.dirty(value); | |
| @override | |
| PasswordValidationError? validator(String value) { | |
| if (value.isEmpty) { | |
| return PasswordValidationError.empty; | |
| } else if (value.length < 6) { | |
| return PasswordValidationError.invalid; | |
| } else { | |
| return null; | |
| } | |
| } | |
| static String? showPasswordErrorMessage(PasswordValidationError? error) { | |
| if (error == PasswordValidationError.empty) { | |
| return 'Empty password'; | |
| } else if (error == PasswordValidationError.invalid) { | |
| return 'Invalid password'; | |
| } else { | |
| return null; | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| part 'register_state.dart'; | |
| final registerProvider = | |
| StateNotifierProvider.autoDispose<RegisterController, RegisterState>( | |
| (ref) => RegisterController(ref.watch(authRepositoryProvider)), | |
| ); | |
| class RegisterController extends StateNotifier<RegisterState> { | |
| final AuthenticationRepository _authenticationRepository; | |
| RegisterController(this._authenticationRepository) | |
| : super(const RegisterState()); | |
| void onNameChange(String value) { | |
| final name = Name.dirty(value); | |
| state = state.copyWith( | |
| name: name, | |
| status: Formz.validate([ | |
| name, | |
| state.email, | |
| state.password, | |
| ]), | |
| ); | |
| } | |
| void onEmailChange(String value) { | |
| final email = Email.dirty(value); | |
| state = state.copyWith( | |
| email: email, | |
| status: Formz.validate( | |
| [ | |
| state.name, | |
| email, | |
| state.password, | |
| ], | |
| ), | |
| ); | |
| } | |
| void onPasswordChange(String value) { | |
| final password = Password.dirty(value); | |
| state = state.copyWith( | |
| password: password, | |
| status: Formz.validate( | |
| [ | |
| state.name, | |
| state.email, | |
| password, | |
| ], | |
| ), | |
| ); | |
| } | |
| void signUpWithEmailAndPassword() async { | |
| if (!state.status.isValidated) return; | |
| state = state.copyWith(status: FormzStatus.submissionInProgress); | |
| try { | |
| await _authenticationRepository.signUpWithEmailAndPassword( | |
| email: state.email.value, | |
| password: state.password.value, | |
| ); | |
| state = state.copyWith(status: FormzStatus.submissionSuccess); | |
| } on SignUpWithEmailAndPasswordFailure catch (e) { | |
| state = state.copyWith( | |
| status: FormzStatus.submissionFailure, errorMessage: e.code); | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class RegisterScreen extends ConsumerWidget { | |
| const RegisterScreen({super.key}); | |
| @override | |
| Widget build(BuildContext context, WidgetRef ref) { | |
| ref.listen<RegisterState>( | |
| registerProvider, | |
| (previous, current) { | |
| if (current.status.isSubmissionInProgress) { | |
| showDialog( | |
| context: context, | |
| builder: (_) => const SimpleDialog( | |
| title: Padding( | |
| padding: EdgeInsets.all(32.0), | |
| child: Text("Loading..."), | |
| ), | |
| ), | |
| ); | |
| } else if (current.status.isSubmissionFailure) { | |
| Navigator.of(context).pop(); | |
| ScaffoldMessenger.of(context).showSnackBar( | |
| SnackBar( | |
| content: Text("${current.errorMessage}"), | |
| ), | |
| ); | |
| } else if (current.status.isSubmissionSuccess) { | |
| Navigator.of(context).pop(); | |
| Navigator.of(context).pop(); | |
| ScaffoldMessenger.of(context).showSnackBar( | |
| const SnackBar( | |
| content: Text("Registration Successful."), | |
| ), | |
| ); | |
| } | |
| }, | |
| ); | |
| return AuthView( | |
| child: ListView(children: [ | |
| const Text( | |
| 'SIGN UP', | |
| style: TextStyle( | |
| fontWeight: FontWeight.w700, | |
| fontSize: 28, | |
| color: Color(0xFF8BD8F4)), | |
| ), | |
| const EmailField(), | |
| const SizedBox(height: 32), | |
| const NameField(), | |
| const SizedBox(height: 32), | |
| const PasswordField(), | |
| const SizedBox(height: 32), | |
| Row( | |
| mainAxisAlignment: MainAxisAlignment.start, | |
| children: [ | |
| SizedBox( | |
| height: 24, | |
| width: 24, | |
| child: Checkbox( | |
| materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, | |
| shape: RoundedRectangleBorder( | |
| borderRadius: BorderRadius.circular(5), | |
| ), | |
| value: false, | |
| onChanged: (_) {}), | |
| ), | |
| const SizedBox( | |
| width: 10, | |
| ), | |
| RichText( | |
| overflow: TextOverflow.ellipsis, | |
| text: const TextSpan( | |
| text: 'Yes, I agree to the ', | |
| style: TextStyle(fontSize: 13, color: Color(0xFF2B415F)), | |
| children: [ | |
| TextSpan( | |
| text: 'Terms & Services', | |
| style: TextStyle( | |
| fontSize: 13, | |
| color: Color(0xFF2B415F), | |
| fontWeight: FontWeight.w700), | |
| ) | |
| ], | |
| ), | |
| ) | |
| ], | |
| ), | |
| const SizedBox( | |
| height: 40, | |
| ), | |
| const SignUpButton(), | |
| const SizedBox( | |
| height: 28, | |
| ), | |
| RichText( | |
| text: TextSpan( | |
| text: 'Existing User? ', | |
| style: const TextStyle( | |
| color: Color(0xFF2B415F), | |
| fontSize: 16, | |
| ), | |
| children: [ | |
| TextSpan( | |
| text: 'Login', | |
| recognizer: TapGestureRecognizer() | |
| ..onTap = () { | |
| Navigator.of(context).pop(); | |
| }, | |
| style: const TextStyle( | |
| color: Color(0xFF2B415F), | |
| fontSize: 16, | |
| fontWeight: FontWeight.w700, | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ]), | |
| ); | |
| } | |
| } | |
| class EmailField extends ConsumerWidget { | |
| const EmailField({Key? key}) : super(key: key); | |
| @override | |
| Widget build(BuildContext context, WidgetRef ref) { | |
| final signUpState = ref.watch(registerProvider); | |
| final showError = signUpState.email.invalid; | |
| final signUpController = ref.read(registerProvider.notifier); | |
| return TextInputField( | |
| hintText: "Email", | |
| errorText: showError | |
| ? Email.showEmailErrorMessage(signUpState.email.error) | |
| : null, | |
| onChanged: (email) => signUpController.onEmailChange(email), | |
| ); | |
| } | |
| } | |
| class NameField extends ConsumerWidget { | |
| const NameField({Key? key}) : super(key: key); | |
| @override | |
| Widget build(BuildContext context, WidgetRef ref) { | |
| final signUpState = ref.watch(registerProvider); | |
| final showError = signUpState.name.invalid; | |
| final signUpController = ref.read(registerProvider.notifier); | |
| return TextInputField( | |
| hintText: "Name", | |
| errorText: | |
| showError ? Name.showNameErrorMessage(signUpState.name.error) : null, | |
| onChanged: (name) => signUpController.onNameChange(name), | |
| ); | |
| } | |
| } | |
| class PasswordField extends ConsumerWidget { | |
| const PasswordField({Key? key}) : super(key: key); | |
| @override | |
| Widget build(BuildContext context, WidgetRef ref) { | |
| final signUpState = ref.watch(registerProvider); | |
| final showError = signUpState.password.invalid; | |
| final signUpController = ref.read(registerProvider.notifier); | |
| return TextInputField( | |
| hintText: "Password", | |
| obscureText: true, | |
| errorText: showError | |
| ? Password.showPasswordErrorMessage(signUpState.password.error) | |
| : null, | |
| onChanged: (password) => signUpController.onPasswordChange(password), | |
| ); | |
| } | |
| } | |
| class SignUpButton extends ConsumerWidget { | |
| const SignUpButton({Key? key}) : super(key: key); | |
| @override | |
| Widget build(BuildContext context, WidgetRef ref) { | |
| final signUpState = ref.watch(registerProvider); | |
| final signUpController = ref.read(registerProvider.notifier); | |
| final bool isValidated = signUpState.status.isValidated; | |
| return SizedBox( | |
| height: 68, | |
| width: MediaQuery.of(context).size.height, | |
| child: DecoratedBox( | |
| decoration: const BoxDecoration( | |
| gradient: LinearGradient( | |
| begin: Alignment.topLeft, | |
| end: Alignment(0.8, 1), | |
| colors: [ | |
| Color(0xffF6D458), | |
| Color(0xffE6BB4C), | |
| ], | |
| tileMode: TileMode.mirror, | |
| ), | |
| borderRadius: BorderRadius.only( | |
| bottomLeft: Radius.circular(10), | |
| topRight: Radius.circular(10), | |
| ), | |
| ), | |
| child: ElevatedButton( | |
| style: ElevatedButton.styleFrom( | |
| elevation: 0, | |
| shape: const RoundedRectangleBorder( | |
| borderRadius: BorderRadius.only( | |
| bottomLeft: Radius.circular(10), | |
| topRight: Radius.circular(10), | |
| ), | |
| ), | |
| ), | |
| onPressed: isValidated | |
| ? () => signUpController.signUpWithEmailAndPassword() | |
| : null, | |
| child: const Text( | |
| "PROCEED", | |
| style: TextStyle(fontSize: 17, fontWeight: FontWeight.w700), | |
| ), | |
| ), | |
| ), | |
| ); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| part of "register_controller.dart"; | |
| class RegisterState extends Equatable { | |
| final Name name; | |
| final Email email; | |
| final Password password; | |
| final FormzStatus status; | |
| final String? errorMessage; | |
| const RegisterState({ | |
| this.name = const Name.pure(), | |
| this.email = const Email.pure(), | |
| this.password = const Password.pure(), | |
| this.status = FormzStatus.pure, | |
| this.errorMessage, | |
| }); | |
| RegisterState copyWith({ | |
| Name? name, | |
| Email? email, | |
| Password? password, | |
| FormzStatus? status, | |
| String? errorMessage, | |
| }) { | |
| return RegisterState( | |
| name: name ?? this.name, | |
| email: email ?? this.email, | |
| password: password ?? this.password, | |
| status: status ?? this.status, | |
| errorMessage: errorMessage ?? this.errorMessage, | |
| ); | |
| } | |
| @override | |
| List<Object?> get props => [ | |
| name, | |
| email, | |
| password, | |
| status, | |
| ]; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment