Skip to content

Instantly share code, notes, and snippets.

@arafaysaleem
Created April 8, 2024 10:34
Show Gist options
  • Save arafaysaleem/036021e49a1fdc650d5cbc2a73bb74bc to your computer and use it in GitHub Desktop.
Save arafaysaleem/036021e49a1fdc650d5cbc2a73bb74bc to your computer and use it in GitHub Desktop.
Custom form field for validating any widget in flutter
import 'package:flutter/material.dart';
// Helpers
import '../../helpers/constants/constants.dart';
// Widgets
import 'custom_value_listener.dart';
typedef OnChangedCallback<T> = void Function(T?);
typedef OnValidateCallback<T> = String? Function(T?)?;
typedef OnSavedCallback<T> = void Function(T?)?;
class CustomFormErrorStyle {
/// The alignment of the error text.
final Alignment errorAlign;
/// The flag used to enable/disable error border.
final bool showErrorBorder;
/// The flag used to enable/disable focused border.
final bool showErrorMessage;
/// The style used for displaying error text.
final TextStyle? textStyle;
const CustomFormErrorStyle({
this.errorAlign = Alignment.centerRight,
this.showErrorBorder = false,
this.showErrorMessage = true,
this.textStyle = const TextStyle(
fontSize: 14,
color: AppColors.redColor,
),
});
}
/// [T] is any type of value that is passed to the [onSaved], [onChanged],
/// and [validator] callback.
class CustomFormField<T> extends StatelessWidget {
/// An optional method to call with the final value when the
/// form is saved via [FormState.save].
final OnSavedCallback<T> onSaved;
/// An optional method that validates an input. Returns an error
/// string to display if the input is invalid, or null otherwise.
final OnValidateCallback<T> validator;
/// An optional method to call with the value when the
/// field is changed
final OnChangedCallback<T>? onChanged;
final ValueNotifier<T> controller;
/// The style used for displaying error.
final CustomFormErrorStyle errorStyle;
/// The width of the error text.
final double? width;
/// The function used to build the widget
final FormFieldBuilder<T> builder;
const CustomFormField({
required this.controller,
required this.builder,
super.key,
this.onSaved,
this.width,
this.onChanged,
this.validator,
this.errorStyle = const CustomFormErrorStyle(),
});
@override
Widget build(BuildContext context) {
return FormField<T>(
initialValue: controller.value,
validator: validator,
onSaved: onSaved,
builder: (state) {
return Column(
children: [
// Display field
CustomValueListener(
controller: controller,
onChanged: () {
state
..didChange(controller.value)
..validate();
onChanged?.call(controller.value);
},
child: builder(state),
),
// Error text
if (state.hasError && errorStyle.showErrorMessage) ...[
Insets.gapH3,
SizedBox(
width: width,
child: Align(
alignment: errorStyle.errorAlign,
child: Text(
state.errorText!,
style: errorStyle.textStyle,
),
),
),
],
],
);
},
);
}
}
class CustomValueListener<T> extends StatefulWidget {
final Widget child;
final ValueNotifier<T> controller;
final VoidCallback onChanged;
const CustomValueListener({
required this.child,
required this.controller,
required this.onChanged,
super.key,
});
@override
_CustomValueListenerState createState() => _CustomValueListenerState();
}
class _CustomValueListenerState extends State<CustomValueListener> {
@override
void initState() {
super.initState();
widget.controller.addListener(widget.onChanged);
}
@override
void didUpdateWidget(covariant CustomValueListener oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.controller != oldWidget.controller) {
oldWidget.controller.removeListener(widget.onChanged);
widget.controller.addListener(widget.onChanged);
}
}
@override
void dispose() {
widget.controller.removeListener(widget.onChanged);
super.dispose();
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}
// 1. wrap any child
CustomFormField<T?>(
controller: _controller!,
width: width,
validator: validator,
onSaved: onSaved,
onChanged: onChanged,
errorStyle: errorStyle,
builder: _builder,
)
// 2. Wrap multiple formfields in a form widget
Form(
key: formKey,
child: Column(
children: [
CustomFormField<int>(...),
CustomFormField<DateTime>(...),
CustomFormField<String>(...),
]
),
)
// 3. Run all form fields' validators
if (!formKey.currentState!.validate()) return;
...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment