Skip to content

Instantly share code, notes, and snippets.

@hawkkiller
Last active February 9, 2024 13:56
Show Gist options
  • Select an option

  • Save hawkkiller/f463f3ebe7b5370c70553f9b25a206f4 to your computer and use it in GitHub Desktop.

Select an option

Save hawkkiller/f463f3ebe7b5370c70553f9b25a206f4 to your computer and use it in GitHub Desktop.
Forms that support async validation
import 'dart:async';
import 'package:flutter/material.dart';
typedef ValueBuilder<T> = Widget Function(BuildContext context, T value);
class GoodForm extends StatefulWidget {
const GoodForm({
required this.builder,
super.key,
});
/// Builder that is used to build the form
final ValueBuilder<GoodFormState> builder;
/// Returns the [GoodFormState] from the closest [GoodForm] ancestor
static GoodFormState? of(BuildContext context, {bool listen = true}) {
final result = listen
? context.dependOnInheritedWidgetOfExactType<_GoodFormInherited>()
: context.getElementForInheritedWidgetOfExactType<_GoodFormInherited>()?.widget
as _GoodFormInherited?;
return result?._state;
}
@override
State<GoodForm> createState() => GoodFormState();
}
class GoodFormState extends State<GoodForm> {
// list of all the form fields
final _formInputs = <GoodFormInputState<Object>>{};
void _registerField(GoodFormInputState<Object> field) {
_formInputs.add(field);
}
void _unregisterField(GoodFormInputState<Object> field) {
_formInputs.remove(field);
}
Future<bool> validate() async {
final futures =
_formInputs.where((element) => element._validate).map((field) => field.validate());
final isValid = await Future.wait(futures).then((validations) {
return validations.every((isValid) => isValid);
}, onError: (Object error) {
return false;
});
return isValid;
}
@override
Widget build(BuildContext context) {
return _GoodFormInherited(
state: this,
generation: 0,
child: widget.builder(context, this),
);
}
}
class _GoodFormInherited extends InheritedWidget {
const _GoodFormInherited({
required GoodFormState state,
required int generation,
required super.child,
}) : _state = state,
_generation = generation;
final GoodFormState _state;
final int _generation;
@override
bool updateShouldNotify(_GoodFormInherited oldWidget) => _generation != oldWidget._generation;
}
/// Validator for the form input
///
/// [T] is the type of the value that is being validated
typedef GoodFormInputValidator<T> = FutureOr<String?> Function();
/// Form input widget
class GoodFormInput<T extends Object> extends StatefulWidget {
const GoodFormInput({
required this.validator,
required this.builder,
this.validate = true,
this.restorationId,
super.key,
});
/// Validator that is used to validate the form field
final GoodFormInputValidator<T?> validator;
/// Builder that is used to build the form field
final ValueBuilder<GoodFormInputState> builder;
/// Restoration id for the form field
final String? restorationId;
/// Whether to validate the form field
final bool validate;
@override
State<GoodFormInput> createState() => GoodFormInputState<T>();
}
class GoodFormInputState<T extends Object> extends State<GoodFormInput<T>> with RestorationMixin {
final _error = RestorableStringN(null);
bool _validate = true;
String? get error => _error.value;
@override
void initState() {
super.initState();
_validate = widget.validate;
}
@override
void deactivate() {
GoodForm.of(context, listen: false)?._unregisterField(this);
super.deactivate();
}
@override
void didUpdateWidget(covariant GoodFormInput<T> oldWidget) {
_validate = widget.validate;
super.didUpdateWidget(oldWidget);
}
@override
String? get restorationId => widget.restorationId;
@override
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
registerForRestoration(_error, 'field_error');
}
void reportError(String? error) {
setState(() {
_error.value = error;
});
}
Future<bool> validate() async {
final error = await widget.validator();
reportError(error);
return error == null;
}
@override
Widget build(BuildContext context) {
GoodForm.of(context)?._registerField(this);
return widget.builder(
context,
this,
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment