Skip to content

Instantly share code, notes, and snippets.

@tayormi
Last active March 17, 2023 14:31
Show Gist options
  • Select an option

  • Save tayormi/d3b8cb6fd8710810268cf57c068f60c0 to your computer and use it in GitHub Desktop.

Select an option

Save tayormi/d3b8cb6fd8710810268cf57c068f60c0 to your computer and use it in GitHub Desktop.
Riverpod Field Validation
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;
}
}
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;
}
}
}
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;
}
}
}
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);
}
}
}
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),
),
),
),
);
}
}
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