Skip to content

feat: #1480 support error builder #1486

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/workflows/base-beta.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Base (beta)

on:
schedule:
- cron: '0 0 */15 * *' # Runs every 15 days for verifying changes on Flutter beta channel
push:
branches: [beta]
tags:
- '[0-9]+.[0-9]+.[0-9]+-*'

pull_request:
branches: [beta]

workflow_dispatch:

# This ensures that previous jobs for the PR are canceled when PR is updated
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
uses: flutter-form-builder-ecosystem/.github/.github/workflows/minimal-quality.yaml@main
with:
codecov-name: flutter_form_builder
enable-fix-tests: true
fvm-flavor: beta
example:
uses: flutter-form-builder-ecosystem/.github/.github/workflows/build-examples.yaml@main
with:
fvm-flavor: beta


deployment:
permissions:
id-token: write
uses: flutter-form-builder-ecosystem/.github/.github/workflows/deployment.yaml@main
if: ${{ github.ref_type == 'tag' }}
needs: [build, example]
2 changes: 1 addition & 1 deletion .github/workflows/base.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
push:
branches: [main]
tags:
- '[0-9]+.[0-9]+.[0-9]+*'
- '[0-9]+.[0-9]+.[0-9]+'

pull_request:
branches: [main]
Expand Down
14 changes: 14 additions & 0 deletions example/lib/sources/complete_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,20 @@ class _CompleteFormState extends State<CompleteForm> {
validator: FormBuilderValidators.compose([
FormBuilderValidators.min(6),
]),
errorBuilder: (context, errorText) => TextButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(errorText),
duration: const Duration(seconds: 2),
),
);
},
child: Text(
errorText,
style: const TextStyle(color: Colors.red),
),
),
onChanged: _onChanged,
min: 0.0,
max: 10.0,
Expand Down
4 changes: 2 additions & 2 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ packages:
dependency: transitive
description:
name: async
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
url: "https://pub.dev"
source: hosted
version: "2.12.0"
version: "2.13.0"
boolean_selector:
dependency: transitive
description:
Expand Down
1 change: 1 addition & 0 deletions lib/src/fields/form_builder_checkbox.dart
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ class FormBuilderCheckbox extends FormBuilderFieldDecoration<bool> {
super.onReset,
super.focusNode,
super.restorationId,
super.errorBuilder,
required this.title,
this.activeColor,
this.autofocus = false,
Expand Down
1 change: 1 addition & 0 deletions lib/src/fields/form_builder_checkbox_group.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class FormBuilderCheckboxGroup<T> extends FormBuilderFieldDecoration<List<T>> {
super.onReset,
super.focusNode,
super.restorationId,
super.errorBuilder,
required this.options,
this.activeColor,
this.checkColor,
Expand Down
1 change: 1 addition & 0 deletions lib/src/fields/form_builder_choice_chips.dart
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ class FormBuilderChoiceChips<T> extends FormBuilderFieldDecoration<T> {
super.onChanged,
super.valueTransformer,
super.onReset,
super.errorBuilder,
this.alignment = WrapAlignment.start,
this.avatarBorder = const CircleBorder(),
this.backgroundColor,
Expand Down
1 change: 1 addition & 0 deletions lib/src/fields/form_builder_date_range_picker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class FormBuilderDateRangePicker
super.onReset,
super.focusNode,
super.restorationId,
super.errorBuilder,
required this.firstDate,
required this.lastDate,
this.format,
Expand Down
1 change: 1 addition & 0 deletions lib/src/fields/form_builder_date_time_picker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ class FormBuilderDateTimePicker extends FormBuilderFieldDecoration<DateTime> {
super.onReset,
super.focusNode,
super.restorationId,
super.errorBuilder,
this.inputType = InputType.both,
this.scrollPadding = const EdgeInsets.all(20.0),
this.cursorWidth = 2.0,
Expand Down
1 change: 1 addition & 0 deletions lib/src/fields/form_builder_dropdown.dart
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ class FormBuilderDropdown<T> extends FormBuilderFieldDecoration<T> {
super.onReset,
super.focusNode,
super.restorationId,
super.errorBuilder,
required this.items,
this.isExpanded = true,
this.isDense = true,
Expand Down
1 change: 1 addition & 0 deletions lib/src/fields/form_builder_filter_chips.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class FormBuilderFilterChips<T> extends FormBuilderFieldDecoration<List<T>> {
super.initialValue,
required super.name,
super.restorationId,
super.errorBuilder,
required this.options,
this.alignment = WrapAlignment.start,
this.avatarBorder = const CircleBorder(),
Expand Down
9 changes: 5 additions & 4 deletions lib/src/fields/form_builder_radio_group.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ class FormBuilderRadioGroup<T> extends FormBuilderFieldDecoration<T> {
required super.name,
required this.options,
super.initialValue,
super.onChanged,
super.valueTransformer,
super.onReset,
super.restorationId,
super.errorBuilder,
this.activeColor,
this.controlAffinity = ControlAffinity.leading,
this.disabled,
Expand All @@ -54,10 +59,6 @@ class FormBuilderRadioGroup<T> extends FormBuilderFieldDecoration<T> {
this.wrapSpacing = 0.0,
this.wrapTextDirection,
this.wrapVerticalDirection = VerticalDirection.down,
super.onChanged,
super.valueTransformer,
super.onReset,
super.restorationId,
this.itemDecoration,
}) : super(
builder: (FormFieldState<T?> field) {
Expand Down
1 change: 1 addition & 0 deletions lib/src/fields/form_builder_range_slider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ class FormBuilderRangeSlider extends FormBuilderFieldDecoration<RangeValues> {
super.autovalidateMode = AutovalidateMode.disabled,
super.onReset,
super.focusNode,
super.errorBuilder,
required this.min,
required this.max,
this.divisions,
Expand Down
1 change: 1 addition & 0 deletions lib/src/fields/form_builder_slider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ class FormBuilderSlider extends FormBuilderFieldDecoration<double> {
super.onReset,
super.focusNode,
super.restorationId,
super.errorBuilder,
required this.min,
required this.max,
this.divisions,
Expand Down
1 change: 1 addition & 0 deletions lib/src/fields/form_builder_switch.dart
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ class FormBuilderSwitch extends FormBuilderFieldDecoration<bool> {
super.onReset,
super.focusNode,
super.restorationId,
super.errorBuilder,
required this.title,
this.activeColor,
this.activeTrackColor,
Expand Down
1 change: 1 addition & 0 deletions lib/src/fields/form_builder_text_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ class FormBuilderTextField extends FormBuilderFieldDecoration<String> {
super.focusNode,
super.restorationId,
String? initialValue,
super.errorBuilder,
this.readOnly = false,
this.maxLines = 1,
this.obscureText = false,
Expand Down
1 change: 1 addition & 0 deletions lib/src/form_builder_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class FormBuilderField<T> extends FormField<T> {
super.validator,
super.restorationId,
required super.builder,
super.errorBuilder,
required this.name,
this.valueTransformer,
this.onChanged,
Expand Down
22 changes: 15 additions & 7 deletions lib/src/form_builder_field_decoration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class FormBuilderFieldDecoration<T> extends FormBuilderField<T> {
super.onChanged,
super.onReset,
super.focusNode,
super.errorBuilder,
required super.builder,
this.decoration = const InputDecoration(),
}) : assert(
Expand All @@ -45,15 +46,22 @@ class FormBuilderFieldDecorationState<
F get widget => super.widget as F;

/// Get the decoration with the current state
InputDecoration get decoration => widget.decoration.copyWith(
// Read only allow show error to support property skipDisabled
errorText:
InputDecoration get decoration {
final String? efectiveErrorText =
widget.enabled || readOnly
? widget.decoration.errorText ?? errorText
: null,
enabled:
widget.decoration.enabled ? widget.enabled : widget.decoration.enabled,
);
: null;

return widget.decoration.copyWith(
// Read only allow show error to support property skipDisabled
errorText: widget.errorBuilder != null ? null : efectiveErrorText,
error:
widget.errorBuilder != null && efectiveErrorText != null
? widget.errorBuilder!(context, efectiveErrorText)
: null,
enabled: widget.decoration.enabled ? widget.enabled : false,
);
}

@override
bool get hasError => super.hasError || widget.decoration.errorText != null;
Expand Down
4 changes: 2 additions & 2 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ packages:
dependency: transitive
description:
name: async
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
url: "https://pub.dev"
source: hosted
version: "2.12.0"
version: "2.13.0"
boolean_selector:
dependency: transitive
description:
Expand Down
47 changes: 47 additions & 0 deletions test/src/form_builder_field_decoration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,53 @@ void main() {
// The field should be valid again
expect(decorationFieldKey.currentState?.isValid, isTrue);
});

testWidgets('when set errorBuilder then show a custom widget on error', (
widgetTester,
) async {
final decorationFieldKey = GlobalKey<FormBuilderFieldDecorationState>();
const name = 'testField';
const errorTextField = 'error text field';
final errorTextKey = Key('errorTextKey');
final widget = FormBuilderFieldDecoration<String>(
key: decorationFieldKey,
name: name,
errorBuilder: (context, errorText) {
return Text(
errorText,
key: errorTextKey,
style: const TextStyle(color: Colors.red),
);
},
builder: (FormFieldState<String?> field) {
return InputDecorator(
decoration: (field as FormBuilderFieldDecorationState).decoration,
child: TextField(
onChanged: (value) {
field.didChange(value);
},
),
);
},
);

await widgetTester.pumpWidget(buildTestableFieldWidget(widget));

// Initially, the field should be valid
expect(decorationFieldKey.currentState?.isValid, isTrue);

decorationFieldKey.currentState?.invalidate(errorTextField);

// The field should be invalid
expect(decorationFieldKey.currentState?.isValid, isFalse);

await widgetTester.pumpAndSettle();

// Check if the custom error widget is displayed
expect(find.text(errorTextField), findsOneWidget);
expect(find.byKey(errorTextKey), findsOneWidget);
});

group('decoration enabled -', () {
testWidgets(
'when change the error text then the field should be invalid',
Expand Down