Last active
February 9, 2024 13:56
-
-
Save hawkkiller/f463f3ebe7b5370c70553f9b25a206f4 to your computer and use it in GitHub Desktop.
Forms that support async 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 '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