form_ui_builder 0.1.0 copy "form_ui_builder: ^0.1.0" to clipboard
form_ui_builder: ^0.1.0 copied to clipboard

A Flutter package that helps you build dynamic and customizable user interfaces quickly and easily.

form_ui_builder #

Renders a form UI from a server-supplied JSON schema, runs the cross-field logic and validation, and hands your app back a validated, submit-ready payload.

  • Schema in, form out — give it the JSON, it builds the widgets.
  • Cross-field logic — show/hide rules, dependent-value constraints, auto-calculated totals.
  • Validation — per-field as the user types, plus a whole-form sweep before submit.
  • Embeddable — drop the form into your own layout, or use the ready-made full page.

Install #

dependencies:
  form_ui_builder: ^0.1.0

Quick start #

1. Initialize #

The package resolves its internals from a service locator. Call this once, before anything else:

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await setupServiceLocator();
  runApp(const MyApp());
}

2. Build the form logic from your schema #

final schema = jsonDecode(responseBody) as List<dynamic>;

final result = buildFormLogicBundleFromSchema(schema);

result.fold(
  (Failure failure) => print(failure.message),
  (FormLogicBundle bundle) => _show(bundle),
);

buildFormLogicBundleFromSchema walks every page and section down to the leaf elements. If you have already flattened the schema yourself, pass the leaf list to buildFormLogicBundle instead.

Both return an Either<Failure, FormLogicBundle> (fpdart). Nothing throws: a malformed schema comes back as a Failure naming the element that failed.

3. Render it #

Provide the bloc, then render either widget:

BlocProvider(
  create: (_) => FormUiBuilderBloc(initialFormLogicBundle: bundle),
  child: FormUiBuilderScreen(
    title: 'Household survey',
    onSubmit: (FormSubmission submission) {
      // submission.values is Map<String, String>, keyed by database_param
      api.post(submission.toJson());
    },
  ),
)

FormUiBuilderScreen is a full page: app bar, form, submit button.

To place the form inside your own layout — a pane, a dialog, a split view — use FormUiBuilderView, which supplies no chrome of its own. Drive submission by dispatching SubmitForm from your own button:

Row(
  children: [
    Expanded(
      child: FormUiBuilderView(
        maxContentWidth: 720,
        onValidationError: (message) => myBanner.show(message),
        onSubmit: (submission) => api.post(submission.toJson()),
      ),
    ),
    MyOwnSidePanel(
      onSubmitPressed: () =>
          context.read<FormUiBuilderBloc>().add(const SubmitForm()),
    ),
  ],
)

4. Read the result #

onSubmit fires only after the whole form validates. Until then a SubmitForm marks the offending fields, moves focus to the first one, and reports through onValidationError.

class FormSubmission {
  /// Answers keyed by `database_param`. Visible fields only.
  final Map<String, String> values;

  /// Every element, hidden ones included.
  final List<FormFieldResult> fields;

  Map<String, dynamic> toJson();
}

Values are always strings: multi-select checkboxes are comma separated, dates and times are ISO-8601.

Who owns the FocusNodes #

FormLogicBundle holds a FocusNode per editable element.

  • Pass a bundle you built to FormUiBuilderBloc(initialFormLogicBundle: ...) and you own those nodes — dispose them when you discard the bundle. The bloc will not.
  • Let the bloc build the bundle itself (dispatch GenerateParameterMap) and it owns and disposes them.

This is what lets you open the same form twice without a "FocusNode used after being disposed".

Schema reference #

The schema is a tree. Nodes with a childrens array are containers (pages, sections); nodes without one are form elements.

Element types #

element_type is matched case-insensitively.

element_type Renders
ElementLabel A static heading. No value.
ElementText Text field. See input_type.
ElementDropdown Single-select dropdown, from element_data.options.
ElementCheckbox Multi-select checkbox group. Value is comma separated.
ElementDate Date field with a picker. Value is ISO-8601.
ElementTime Time field with a picker. Value is ISO-8601.
ElementHidden Not rendered, not submitted.

Any other type renders nothing (and logs in debug builds).

Element fields #

Key Type Meaning
element_id int Unique id. Every map is keyed by this.
element_type String See above.
database_param String Backend field name — the key in FormSubmission.values.
elementLabel object Label, per language. Note the camelCase key.
elementTooltip object Helper text, per language. camelCase.
element_value String? Initial value.
input_type String text, number or mobile. Drives per-keystroke validation.
min_val / max_val num? Range for number inputs.
is_required String "true" / "false".
is_editable String "false" renders the field read-only.
is_disable, is_selection, is_visible String "true" / "false".
is_dependent bool The one real boolean. true starts hidden.
element_data.options array option_id, option_name, p_value per option.
elementOptionDependent object Show/hide rules. camelCase.
validation array Cross-field constraints.
autocalculate array Auto-calculated rollups.
style object? text_style, background_color, font_color, font_size.

The boolean-ish fields are strings, not JSON booleans"true", not true. Only is_dependent is a real boolean.

Localization #

elementLabel, elementTooltip and option_name each carry twelve language keys: en, hi, mr, gu, bn, as, or, te, kn, ta, ur, pa. They are parsed, but the widgets currently render en only, and the package's own error messages are English.

Cross-field constraints #

Each entry of validation[].values[] declares one constraint:

{
  "dependant": "equal",
  "dependent_operator": "10,11",
  "dependent_result": "1"
}

Read as: elements 10 and 11 (the children) must relate to element 1 (the parent) by equal — they must sum to it. dependant may be equal, less, greater, less than equal or greater than equal. An unrecognised keyword is ignored rather than failing the form.

Blank or unparseable ids are skipped, so "dependent_operator": "" is fine.

Auto-calculation #

Each entry of autocalculate[] rolls children up into a parent:

{
  "operation": "sum",
  "operator": "10,11",
  "operator_result": "1"
}

Element 1 becomes the sum of 10 and 11, recomputed as they change. Only sum is implemented.

Show / hide #

elementOptionDependent maps a condition to the ids it governs:

{ "elementOptionDependent": { "42": "10,11", "7 = 3": "12" } }

A bare id is "this option is selected"; a = b compares element a's selected option to option id b. Conditions may be OR-ed with |. When a condition turns false its elements are hidden and reset to their element_value, and they are left out of the submitted payload. Circular references are detected and stopped.

Desktop notes #

The package is desktop-ready and the example runs on Windows.

  • maxContentWidth (default 720) stops fields stretching across a wide window.
  • Tab traversal works; Enter advances to the next field.
  • Dropdowns and date fields do not pop open on focus by default — set autoOpenPickersOnFocus: true for the touch-friendly behavior.
  • Fonts come from your Theme's textTheme unless the schema pins style.font_size.

Example #

example/ is a full app running against a real ~5400-line schema, showing both the full-page and embedded layouts and what comes back from onSubmit.

cd example
flutter run -d windows

License #

See LICENSE.

0
likes
160
points
148
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter package that helps you build dynamic and customizable user interfaces quickly and easily.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

equatable, flutter, flutter_bloc, fpdart, get_it, synchronized

More

Packages that depend on form_ui_builder