-
Notifications
You must be signed in to change notification settings - Fork 29.4k
Description
This is a long one which probably might better be splitted into multiple issues :-)
Following the idea to build beautiful apps in record time,
I'm missing some easy to use widgets for data manipulation.
Since I'm far from being a Flutter expert, I just did some
research and some architectural base work.
My suggestion (still to be discussed and enhanced) is based
around these requirements:
a) provide an easy to understand package for data input
(data is more than just text)
b) supply data object to the form
(not each single value to each single field-widget)
c) Only write data access once in each single field-widget
(unlike 'initialValue' and 'onChanged' which are just like getter and setter)
d) Widget attributes with same functionality must have same names
(unlike 'initialValue' and 'value' or 'onChanged' and 'onValueChanged')
e) Put as much design to the form as possible
(can still be overwritten by children like each field-widget)
f) Each input widget should auto-assign itself to the form
(requires attribute 'path')
g) Each input widget should be usable stand-alone
(requires attributes 'initialValue' and 'onChanged')
h) Each input widget should accept a list of validators
i) Input validators should be reusable
(e.g. validators for 'NotNull', 'Min', 'Max', 'Future', ...)
Of course are some validators only usable for specific types of input values like
'Future' only makes sense for dates. Anyway there could be multiple input widgets
for dates.
So I came up with this suggestions:
(numbers are provided for reference, not for order)
- Form extends StatefulWidget => exists
- FormState extends State => exists
- abstract Field extends StatefulWidget // T is the value type
- Field.createState() will never be invoked by the framework
- FieldState extends State<Field>
- FieldState contains the current value of type T
- InputWidget extends Field // T is the value type of this input widget
- InputWidget.createState() => InputWidgetState
- All InputWidget should have a ".adaptive" constructor like
Switch
(I don't know if this is possible, but would be great to have)
Comment for 8: do not provide a builder in InputWidget constructor when calling super.
This might make sense in TextFormField (small reduction of instance data)
but prevents access to instance data in InputWidgetState and its parent FieldState.
A better approach would be to have
FieldState.buildField( BuildContext context, Widget builder )
being invoked by InputWidget.build().
This design would make it easy to build additional input widgets.
Assigments of attributes could go like this:
(all field are optional unless otherwise noted)
Widget / Parameter / Type / Description
Form / key / Key / standard
Form / autovalidate / bool / =false. 'true' will validate input field on every change
Form / child / Widget / required. This one normally is a layout widget (e.g. Column)
Form / decoration / InputDecoration / used by subsequent input widgets, not by form itself
Form / enabled / bool / propagated to all fields below this form which did not overwrote it
Form / onChanged / ValueSetter / invoked on every value change of any 'InputWidget'
Form / onSaved / ValueSetter / invoked on 'Form.save()'
Form / onWillPop / as documented
Form / value / Map<String,dynamic> / map of nested values. Used by input widget as value
Field / key / Key / standard. Makes sense if input widget is stand-alone or for inter-field-dependencies
Field / autovalidate / bool / =false. 'true' invokes validate() on every value change. Overwrites 'autovalidate' of Form
Field / decoration / InputDecoration / label, error, hint, frame, ...
Field / enabled / bool / used when stand-alone. As form child overwrites Form.enabled which will not be propagated into this field
Field / initialValue / T / used to fill field.value. Precedence over using 'Form.value[path]'
Field / onChanged / ValueSetter / will be invoked on every change of the fields value
Field / onSaved / ValueSetter / will be invoked on Field.save() which also writes 'Form.value[path] = Field.value'
Field / path / String / used to access 'Form.value[]'. If null then 'onChanged' or 'onSaved' must not be null
Field / validators / List / invoked on 'Field.validate()'. First validator returning a string instead of null sets errorText of field and breaks the loop
InputWidget has all parameters of a Field plus its specific params.
Some methods are worth to be discussed as well:
InputWidgetState.initState()
must invoke super.initState()
FieldState.initState()
registers itself if a 'Form' predecessor exists.
sets instance variable 'value' to 'initialValue'. If 'initialValue' is null then 'value = Form.value[ path ]'
FieldState.build()
is an empty method which will never be invoked. Instead 'buildField' will be invoked by 'InputWidgetState.build()'.
FieldState.buildField( BuildContext context, Widget builder )
uses parameter 'Field.enabled'. If null then uses current 'Form.enabled' (not attribute 'Form.enabled' from constructor!)
returns an InputDecorator with child = builder and decoration = most appropriate decoration from field itself of from form or from somewhere above (?)
FieldState.save()
if ( path != null ) then Form.value[ path ] = FieldState.value;
if ( onSaved != null ) then onSaved( value );
FieldState.validate()
for ( InputValidator validator in Field.validators) {
if (( errorText = validator.validate()) != null ) then return false;
}
return true;
This will close issue #45609.