Created
April 8, 2024 10:34
-
-
Save arafaysaleem/036021e49a1fdc650d5cbc2a73bb74bc to your computer and use it in GitHub Desktop.
Custom form field for validating any widget in flutter
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 '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, | |
), | |
), | |
), | |
], | |
], | |
); | |
}, | |
); | |
} | |
} |
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
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; | |
} | |
} |
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
// 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