Skip to content
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
4 changes: 3 additions & 1 deletion .github/workflows/base.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,13 @@ jobs:
- name: Install dependencies
run: flutter pub get
- name: Format code
run: dart format --set-exit-if-changed .
run: dart format --set-exit-if-changed lib/ test/ example/
- name: Analyze static code
run: flutter analyze
- name: Run tests
run: flutter test --coverage
- name: Run fixes tests
run: dart fix --compare-to-golden test_fixes/
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
Expand Down
4 changes: 4 additions & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
include: package:flutter_lints/flutter.yaml

analyzer:
exclude:
- "test_fixes/**"
2 changes: 1 addition & 1 deletion example/lib/minimal_code_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class _ExamplePageState extends State<_ExamplePage> {
key: _formKey,
child: Column(
children: [
FormBuilderFilterChip<String>(
FormBuilderFilterChips<String>(
decoration: const InputDecoration(
labelText: 'The language of my people',
enabled: false,
Expand Down
4 changes: 2 additions & 2 deletions example/lib/sources/complete_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ class _CompleteFormState extends State<CompleteForm> {
FormBuilderValidators.maxLength(3),
]),
),
FormBuilderFilterChip<String>(
FormBuilderFilterChips<String>(
autovalidateMode: AutovalidateMode.onUserInteraction,
decoration: const InputDecoration(
labelText: 'The language of my people'),
Expand Down Expand Up @@ -286,7 +286,7 @@ class _CompleteFormState extends State<CompleteForm> {
FormBuilderValidators.maxLength(3),
]),
),
FormBuilderChoiceChip<String>(
FormBuilderChoiceChips<String>(
autovalidateMode: AutovalidateMode.onUserInteraction,
decoration: const InputDecoration(
labelText:
Expand Down
45 changes: 45 additions & 0 deletions lib/fix_data.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
version: 1
transforms:
- title: 'Remove deprecated maxChips property on FormBuilderFilterChip'
date: 2025-01-15
element:
uris: [ 'flutter_form_builder.dart' ]
constructor: ''
inClass: 'FormBuilderFilterChip'
changes:
- kind: 'removeParameter'
name: 'maxChips'
- title: 'Remove deprecated resetIcon property on FormBuilderDateTimePicker'
date: 2025-01-15
element:
uris: [ 'flutter_form_builder.dart' ]
constructor: ''
inClass: 'FormBuilderDateTimePicker'
changes:
- kind: 'removeParameter'
name: 'resetIcon'
- title: 'Remove deprecated onPopInvoked property on FormBuilder'
date: 2025-01-15
element:
uris: [ 'flutter_form_builder.dart' ]
constructor: ''
inClass: 'FormBuilder'
changes:
- kind: 'removeParameter'
name: 'onPopInvoked'
- title: 'Rename FormBuilderChoiceChip to be plural'
date: 2025-01-15
element:
uris: [ 'flutter_form_builder.dart' ]
class: 'FormBuilderChoiceChip'
changes:
- kind: 'rename'
newName: 'FormBuilderChoiceChips'
- title: 'Rename FormBuilderFilterChip to be plural'
date: 2025-01-15
element:
uris: [ 'flutter_form_builder.dart' ]
class: 'FormBuilderFilterChip'
changes:
- kind: 'rename'
newName: 'FormBuilderFilterChips'
1 change: 1 addition & 0 deletions lib/src/extensions/generic_validator.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
extension GenericValidator<T> on T? {
/// Check if the value is empty in a generic way
bool emptyValidator() {
if (this == null) return true;
if (this is Iterable) return (this as Iterable).isEmpty;
Expand Down
8 changes: 4 additions & 4 deletions lib/src/fields/form_builder_choice_chips.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';

/// A list of `Chip`s that acts like radio buttons
class FormBuilderChoiceChip<T> extends FormBuilderFieldDecoration<T> {
class FormBuilderChoiceChips<T> extends FormBuilderFieldDecoration<T> {
/// The list of items the user can select.
final List<FormBuilderChipOption<T>> options;

Expand Down Expand Up @@ -344,7 +344,7 @@ class FormBuilderChoiceChip<T> extends FormBuilderFieldDecoration<T> {
final String? tooltip;

/// Creates a list of `Chip`s that acts like radio buttons
FormBuilderChoiceChip({
FormBuilderChoiceChips({
super.autovalidateMode = AutovalidateMode.disabled,
super.enabled,
super.focusNode,
Expand Down Expand Up @@ -457,12 +457,12 @@ class FormBuilderChoiceChip<T> extends FormBuilderFieldDecoration<T> {
});

@override
FormBuilderFieldDecorationState<FormBuilderChoiceChip<T>, T> createState() =>
FormBuilderFieldDecorationState<FormBuilderChoiceChips<T>, T> createState() =>
_FormBuilderChoiceChipState<T>();
}

class _FormBuilderChoiceChipState<T>
extends FormBuilderFieldDecorationState<FormBuilderChoiceChip<T>, T> {
extends FormBuilderFieldDecorationState<FormBuilderChoiceChips<T>, T> {
void handleFocusChange() {
setState(() {});
}
Expand Down
5 changes: 0 additions & 5 deletions lib/src/fields/form_builder_date_time_picker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,6 @@ class FormBuilderDateTimePicker extends FormBuilderFieldDecoration<DateTime> {
/// to noon. Explicitly set this to `null` to use the current time.
final TimeOfDay initialTime;

@Deprecated(
'This property is no used anymore. Please use decoration.suffixIcon to set your desired icon')
final Widget? resetIcon;

/// Called when an enclosing form is saved. The value passed will be `null`
/// if [format] fails to parse the text.
// final FormFieldSetter<DateTime> onSaved;
Expand Down Expand Up @@ -146,7 +142,6 @@ class FormBuilderDateTimePicker extends FormBuilderFieldDecoration<DateTime> {
this.scrollPadding = const EdgeInsets.all(20.0),
this.cursorWidth = 2.0,
this.enableInteractiveSelection = true,
this.resetIcon = const Icon(Icons.close),
this.initialTime = const TimeOfDay(hour: 12, minute: 0),
this.keyboardType,
this.textAlign = TextAlign.start,
Expand Down
10 changes: 5 additions & 5 deletions lib/src/fields/form_builder_filter_chips.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';

/// Field with chips that acts like a list checkboxes.
class FormBuilderFilterChip<T> extends FormBuilderFieldDecoration<List<T>> {
class FormBuilderFilterChips<T> extends FormBuilderFieldDecoration<List<T>> {
//TODO: Add documentation
final Color? backgroundColor;
final Color? disabledColor;
Expand Down Expand Up @@ -35,7 +35,7 @@ class FormBuilderFilterChip<T> extends FormBuilderFieldDecoration<List<T>> {
final ShapeBorder avatarBorder;

/// Creates field with chips that acts like a list checkboxes.
FormBuilderFilterChip({
FormBuilderFilterChips({
super.autovalidateMode = AutovalidateMode.disabled,
super.enabled,
super.focusNode,
Expand All @@ -59,7 +59,7 @@ class FormBuilderFilterChip<T> extends FormBuilderFieldDecoration<List<T>> {
this.labelPadding,
this.labelStyle,
this.materialTapTargetSize,
this.maxChips,
@Deprecated('Useless property. Please remove it.') this.maxChips,
this.padding,
this.pressElevation,
this.runAlignment = WrapAlignment.start,
Expand Down Expand Up @@ -143,9 +143,9 @@ class FormBuilderFilterChip<T> extends FormBuilderFieldDecoration<List<T>> {
);

@override
FormBuilderFieldDecorationState<FormBuilderFilterChip<T>, List<T>>
FormBuilderFieldDecorationState<FormBuilderFilterChips<T>, List<T>>
createState() => _FormBuilderFilterChipState<T>();
}

class _FormBuilderFilterChipState<T> extends FormBuilderFieldDecorationState<
FormBuilderFilterChip<T>, List<T>> {}
FormBuilderFilterChips<T>, List<T>> {}
5 changes: 5 additions & 0 deletions lib/src/fields/form_builder_text_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,11 @@ class FormBuilderTextField extends FormBuilderFieldDecoration<String> {
this.contentInsertionConfiguration,
this.spellCheckConfiguration,
this.clipBehavior = Clip.hardEdge,
@Deprecated(
'This property will be removed in the next Flutter stable versions. '
'Use FocusNode.canRequestFocus instead. '
'Ref: https://docs.flutter.dev/release/breaking-changes/can-request-focus',
)
this.canRequestFocus = true,
this.cursorErrorColor,
this.cursorOpacityAnimates,
Expand Down
29 changes: 8 additions & 21 deletions lib/src/form_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ class FormBuilder extends StatefulWidget {
/// will rebuild.
final VoidCallback? onChanged;

/// DEPRECATED: Use [onPopInvokedWithResult] instead.
final void Function(bool)? onPopInvoked;

/// {@macro flutter.widgets.navigator.onPopInvokedWithResult}
///
/// {@tool dartpad}
Expand Down Expand Up @@ -105,11 +102,6 @@ class FormBuilder extends StatefulWidget {
required this.child,
this.onChanged,
this.autovalidateMode,
@Deprecated(
'Use onPopInvokedWithResult instead. '
'This feature was deprecated after v3.22.0-12.0.pre.',
)
this.onPopInvoked,
this.onPopInvokedWithResult,
this.initialValue = const <String, dynamic>{},
this.skipDisabled = false,
Expand Down Expand Up @@ -143,6 +135,7 @@ class FormBuilderState extends State<FormBuilder> {
/// Only used to internal logic
bool get focusOnInvalid => _focusOnInvalid;

/// Get if form is enabled
bool get enabled => widget.enabled;

/// Verify if all fields on form are valid.
Expand Down Expand Up @@ -171,6 +164,7 @@ class FormBuilderState extends State<FormBuilder> {
/// Get all fields of form.
FormBuilderFields get fields => _fields;

/// Get all fields values of form.
Map<String, dynamic> get instantValue => Map<String, dynamic>.unmodifiable(
_instantValue.map(
(key, value) => MapEntry(
Expand Down Expand Up @@ -204,15 +198,18 @@ class FormBuilderState extends State<FormBuilder> {
initialValue[name];
}

/// Get a field value by name
void setInternalFieldValue<T>(String name, T? value) {
_instantValue[name] = value;
widget.onChanged?.call();
}

/// Remove a field value by name
void removeInternalFieldValue(String name) {
_instantValue.remove(name);
}

/// Register a field on form
void registerField(String name, FormBuilderFieldState field) {
// Each field must have a unique name. Ideally we could simply:
// assert(!_fields.containsKey(name));
Expand Down Expand Up @@ -242,6 +239,7 @@ class FormBuilderState extends State<FormBuilder> {
);
}

/// Unregister a field on form
void unregisterField(String name, FormBuilderFieldState field) {
assert(
_fields.containsKey(name),
Expand Down Expand Up @@ -270,23 +268,14 @@ class FormBuilderState extends State<FormBuilder> {
}
}

/// Save form values
void save() {
_formKey.currentState!.save();
// Copy values from instant to saved
_savedValue.clear();
_savedValue.addAll(_instantValue);
}

@Deprecated(
'Will be remove to avoid redundancy. Use fields[name]?.invalidate(errorText) instead')
void invalidateField({required String name, String? errorText}) =>
fields[name]?.invalidate(errorText ?? '');

@Deprecated(
'Will be remove to avoid redundancy. Use fields.first.invalidate(errorText) instead')
void invalidateFirstField({required String errorText}) =>
fields.values.first.invalidate(errorText);

/// Validate all fields of form
///
/// Focus to first invalid field when has field invalid, if [focusOnInvalid] is `true`.
Expand Down Expand Up @@ -343,7 +332,7 @@ class FormBuilderState extends State<FormBuilder> {
);
}

/// Reset form to `initialValue`
/// Reset form to `initialValue` set on FormBuilder or on each field.
void reset() {
_formKey.currentState?.reset();
}
Expand Down Expand Up @@ -376,8 +365,6 @@ class FormBuilderState extends State<FormBuilder> {
key: _formKey,
autovalidateMode: widget.autovalidateMode,
onPopInvokedWithResult: widget.onPopInvokedWithResult,
// ignore: deprecated_member_use
onPopInvoked: widget.onPopInvoked,
canPop: widget.canPop,
// `onChanged` is called during setInternalFieldValue else will be called early
child: _FormBuilderScope(
Expand Down
26 changes: 26 additions & 0 deletions lib/src/form_builder_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,17 @@ class FormBuilderFieldState<F extends FormBuilderField<T>, T>
FormBuilderState? _formBuilderState;
bool _touched = false;
bool _dirty = false;

/// The focus node that is used to focus this field.
late FocusNode effectiveFocusNode;

/// The focus attachment for the [effectiveFocusNode].
FocusAttachment? focusAttachment;

@override
F get widget => super.widget as F;

/// Returns the parent [FormBuilderState] if it exists.
FormBuilderState? get formState => _formBuilderState;

/// Returns the initial value, which may be declared at the field, or by the
Expand All @@ -91,18 +96,33 @@ class FormBuilderFieldState<F extends FormBuilderField<T>, T>
widget.valueTransformer == null ? value : widget.valueTransformer!(value);

@override

/// Returns the current error text,
/// which may be a validation error or a custom error text.
String? get errorText => super.errorText ?? _customErrorText;

@override

/// Returns `true` if the field has an error or has a custom error text.
bool get hasError => super.hasError || errorText != null;

@override

/// Returns `true` if the field is valid and has no custom error text.
bool get isValid => super.isValid && _customErrorText == null;

/// Returns `true` if the field is valid.
bool get valueIsValid => super.isValid;

/// Returns `true` if the field has an error.
bool get valueHasError => super.hasError;

/// Returns `true` if the field is enabled and the parent FormBuilder is enabled.
bool get enabled => widget.enabled && (_formBuilderState?.enabled ?? true);

/// Returns `true` if the field is read only.
///
/// See [FormBuilder.skipDisabled] for more information.
bool get readOnly => !(_formBuilderState?.widget.skipDisabled ?? false);

/// Will be true if the field is dirty
Expand Down Expand Up @@ -199,6 +219,10 @@ class FormBuilderFieldState<F extends FormBuilderField<T>, T>
}

@override

/// Reset field value to initial value
///
/// Also reset custom error text if exists, and set [isDirty] to `false`.
void reset() {
super.reset();
didChange(initialValue);
Expand Down Expand Up @@ -276,10 +300,12 @@ class FormBuilderFieldState<F extends FormBuilderField<T>, T>
);
}

/// Focus field
void focus() {
FocusScope.of(context).requestFocus(effectiveFocusNode);
}

/// Scroll to show field
void ensureScrollableVisibility() {
Scrollable.ensureVisible(context);
}
Expand Down
1 change: 1 addition & 0 deletions lib/src/form_builder_field_decoration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class FormBuilderFieldDecorationState<F extends FormBuilderFieldDecoration<T>,
@override
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: widget.enabled || readOnly
Expand Down
Loading
Loading