Skip to content

Provide input widgets to easily build beautiful data manipulation forms in record time #46073

@hlemcke

Description

@hlemcke

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)

  1. Form extends StatefulWidget => exists
  2. FormState extends State => exists
  3. abstract Field extends StatefulWidget // T is the value type
  4. Field.createState() will never be invoked by the framework
  5. FieldState extends State<Field>
  6. FieldState contains the current value of type T
  7. InputWidget extends Field // T is the value type of this input widget
  8. InputWidget.createState() => InputWidgetState
  9. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    a: text inputEntering text in a text field or keyboard related problemsc: proposalA detailed proposal for a change to Flutterf: material designflutter/packages/flutter/material repository.frameworkflutter/packages/flutter repository. See also f: labels.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions