Skip to content
27 changes: 27 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "formz",
"request": "launch",
"type": "dart"
},
{
"name": "async_example",
"cwd": "example",
"request": "launch",
"type": "dart",
"program": "lib/async/main.dart"
},
{
"name": "sync_example",
"cwd": "example",
"request": "launch",
"type": "dart",
"program": "lib/sync/main.dart"
}
]
}
112 changes: 112 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,118 @@ print(joe.error); // null
print(name.displayError); // null
```

## Create a AsyncFormzInput
```dart

class Email extends AsyncFormzInput<String, EmailValidationError> {
const Email(
super.value, {
super.error,
super.validationStatus,
this.isRequired = true,
});

final bool isRequired;

Email copyWith({
String? value,
EmailValidationError? error,
AsyncFormzInputValidationStatus? validationStatus,
bool? isValidating,
bool? isRequired,
}) {
return Email(
value ?? this.value,
error: error ?? this.error,
validationStatus: validationStatus ?? this.validationStatus,
isRequired: isRequired ?? this.isRequired,
);
}

Email copyWithResetError({
String? value,
AsyncFormzInputValidationStatus? validationStatus,
bool? isRequired,
}) {
return Email(
value ?? this.value,
validationStatus: validationStatus ?? this.validationStatus,
isRequired: isRequired ?? this.isRequired,
);
}
}
```

## Create a AsyncFormzInput
```dart
class EmailValidator extends AsyncFormzInputValidator<Email, String, EmailValidationError> {
const EmailValidator({
required EmailRepository emailRepository,
}) : _emailRepository = emailRepository;

final EmailRepository _emailRepository;

@override
bool canValidate(Email input) {
if (input.validationStatus.isValidated) {
return true;
}
final validFormat = RegVal.hasMatch(
input.value,
_kEmailPattern,
);
return validFormat;
}

@override
Future<EmailValidationError?> validate(Email input) async {
if (input.isRequired && input.value.isEmpty) {
return const EmailValidationError.required();
}

final validFormat = RegVal.hasMatch(
input.value,
_kEmailPattern,
);
if (!validFormat) {
return const EmailValidationError.invalidFormat();
}

final alreadyExist = await _emailRepository.findByAddress(input.value);
if (alreadyExist) {
return const EmailValidationError.alreadyExists();
}

return null;
}
}
```

## Interact with a AsyncFormzInput and AsyncFormzInputValidator using Bloc
```dart
Future<void> emailChanged(String value) async {
emit(state.copyWith(
email: state.email.copyWithResetError(value: value),
));
final canValidate = _emailValidator.canValidate(state.email);
if (!canValidate) {
return;
}
emit(state.copyWith(
email: state.email.copyWith(
validationStatus: AsyncFormzInputValidationStatus.validating,
),
));
final error = await _emailValidator.validate(state.email);
emit(state.copyWith(
email: state.email.copyWith(
error: error,
validationStatus: AsyncFormzInputValidationStatus.validated,
),
));
}
```

## Validate Multiple FormzInput Items

```dart
Expand Down
8 changes: 8 additions & 0 deletions example/lib/async/data/repositories/email_repository.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class EmailRepository {
const EmailRepository();

Future<bool> findByAddress(String emailAddress) async {
await Future<void>.delayed(const Duration(seconds: 1));
return true;
}
}
6 changes: 6 additions & 0 deletions example/lib/async/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import 'package:example/async/ui/app.dart';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate the example app from a more "real world usage" point of view, but it defeats the purpose of the example of allowing people to understand what a package API looks like when used.

import 'package:flutter/material.dart';

void main() {
runApp(const App());
}
19 changes: 19 additions & 0 deletions example/lib/async/ui/app.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import 'package:example/async/data/repositories/email_repository.dart';
import 'package:example/async/ui/feature/register/view/register_page.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class App extends StatelessWidget {
const App({super.key});

@override
Widget build(BuildContext context) {
return Provider.value(
value: const EmailRepository(),
child: const MaterialApp(
title: 'Example',
home: RegisterPage(),
),
);
}
}
78 changes: 78 additions & 0 deletions example/lib/async/ui/feature/register/cubit/register_cubit.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import 'package:example/async/ui/feature/register/cubit/register_state.dart';
import 'package:example/async/ui/shared/forms/validators/amount_validator.dart';
import 'package:example/async/ui/shared/forms/validators/email_validator.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:formz/formz.dart';

class RegisterCubit extends Cubit<RegisterState> {
RegisterCubit({
required AmountValidator amountValidator,
required EmailValidator emailValidator,
}) : _amountValidator = amountValidator,
_emailValidator = emailValidator,
super(RegisterState());

final AmountValidator _amountValidator;
final EmailValidator _emailValidator;

Future<void> amountChanged(String value) async {
emit(
state.copyWith(
amount: state.amount.copyWithResetError(
value: value,
validationStatus: AsyncFormzInputValidationStatus.validating,
),
),
);
final error = await _amountValidator.validate(state.amount);
emit(
state.copyWith(
amount: state.amount.copyWith(
error: error,
validationStatus: AsyncFormzInputValidationStatus.validated,
),
),
);
_updateFormState();
}

Future<void> emailChanged(String value) async {
emit(
state.copyWith(
email: state.email.copyWithResetError(value: value),
),
);
final canValidate = _emailValidator.canValidate(state.email);
if (!canValidate) {
return;
}
emit(
state.copyWith(
email: state.email.copyWith(
validationStatus: AsyncFormzInputValidationStatus.validating,
),
),
);
final error = await _emailValidator.validate(state.email);
emit(
state.copyWith(
email: state.email.copyWith(
error: error,
validationStatus: AsyncFormzInputValidationStatus.validated,
),
),
);
_updateFormState();
}

void _updateFormState() {
emit(
state.copyWith(
isFormValid: Formz.validate([
state.amount,
state.email,
]),
),
);
}
}
14 changes: 14 additions & 0 deletions example/lib/async/ui/feature/register/cubit/register_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import 'package:example/async/ui/shared/forms/inputs/amount_input.dart';
import 'package:example/async/ui/shared/forms/inputs/email_input.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'register_state.freezed.dart';

@freezed
class RegisterState with _$RegisterState {
factory RegisterState({
@Default(false) bool isFormValid,
@Default(Amount('', max: 0.5)) Amount amount,
@Default(Email('')) Email email,
}) = _RegisterState;
}
Loading