Skip to content

Quick Start ​

Build a short deviation report in three steps: define it, render it for users to fill in, then open it in the designer. The concept pages extend this same form into the full deviation intake example.

1. Define the form ​

A definition has a flat list of fields, a layout tree under root that places each field once through a FieldSlot, and optional rules.

dart
import 'package:vyuh_form_types/vyuh_form_types.dart';

final deviationReport = FormDefinition(
  key: 'deviation_report',
  title: 'Deviation report',
  description: 'Log a deviation within 24 hours of discovery.',
  fields: [
    TextFormField(
      key: 'title',
      label: 'Title',
      hint: 'e.g. Temperature excursion in cold room 2',
      required: true,
      validations: const [LengthValidation(max: 120)],
    ),
    ChoiceFormField(
      key: 'severity',
      label: 'Severity',
      required: true,
      choices: const [
        FormChoice(value: 'minor', label: 'Minor'),
        FormChoice(value: 'major', label: 'Major'),
        FormChoice(value: 'critical', label: 'Critical'),
      ],
    ),
    DateTimeFormField(key: 'discovered_at', label: 'Discovered', required: true),
    TextFormField(
      key: 'what_happened',
      label: 'What happened',
      multiline: true,
      required: true,
    ),
  ],
  root: FormRoot(
    id: 'root',
    children: [
      FormRow(
        id: 'summary_row',
        columns: 12,
        cells: const [
          FormCell(child: FieldSlot(id: 'slot_title', fieldKey: 'title'), span: 6),
          FormCell(child: FieldSlot(id: 'slot_severity', fieldKey: 'severity'), span: 3),
          FormCell(child: FieldSlot(id: 'slot_discovered', fieldKey: 'discovered_at'), span: 3),
        ],
      ),
      const FieldSlot(id: 'slot_what', fieldKey: 'what_happened'),
    ],
  ),
);

The constructor checks the whole definition. A duplicate key, a slot for an unknown field, or a row whose spans add up to more than 12 throws a FormatException that names the problem, so an invalid definition never exists.

2. Render it with FormView ​

FormView is stateless. You own the values and decide which fields show their messages.

dart
import 'package:flutter/material.dart' hide TextFormField;
import 'package:vyuh_form_kit/vyuh_form_kit.dart';

final class DeviationReportPage extends StatefulWidget {
  const DeviationReportPage({super.key});

  @override
  State<DeviationReportPage> createState() => _DeviationReportPageState();
}

final class _DeviationReportPageState extends State<DeviationReportPage> {
  var _values = <String, Object?>{};
  var _touched = <String>{};

  void _submit() {
    final errors = [
      for (final issue in FormValidationEngine.validate(deviationReport, _values))
        if (!issue.warning) issue,
    ];
    if (errors.isNotEmpty) {
      // Show every field's message, not only the ones the user touched.
      setState(() => _touched = {for (final f in deviationReport.fields) f.key});
      return;
    }
    // Save _values.
  }

  @override
  Widget build(BuildContext context) => Column(
    crossAxisAlignment: CrossAxisAlignment.stretch,
    spacing: 16,
    children: [
      FormView(
        definition: deviationReport,
        values: _values,
        validationKeys: _touched,
        onChanged: (key, value) => setState(() {
          _values = {..._values, key: value};
          _touched = {..._touched, key};
        }),
      ),
      FilledButton(onPressed: _submit, child: const Text('Submit')),
    ],
  );
}

On a wide screen the row lays out 6 + 3 + 3 columns. Below 600 px (the row's stackBelow), or when a cell would be narrower than its field's minimum width, the cells stack into one column.

3. Edit it in the designer ​

FormKitEditor edits a definition through a FormKitEditorController. The controller holds the current definition, the selection and the undo history.

dart
import 'package:flutter/material.dart' hide TextFormField;
import 'package:vyuh_form_kit_editor/vyuh_form_kit_editor.dart';

final class DeviationDesigner extends StatefulWidget {
  const DeviationDesigner({super.key});

  @override
  State<DeviationDesigner> createState() => _DeviationDesignerState();
}

final class _DeviationDesignerState extends State<DeviationDesigner> {
  late final _controller = FormKitEditorController(deviationReport);

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  void _publish() {
    final json = FormCodec.encodeString(_controller.definition);
    // Store json; FormCodec.decodeString(json) restores the same definition.
  }

  @override
  Widget build(BuildContext context) => FormKitEditor(
    controller: _controller,
    subtitle: 'Quality · Forms',
    actions: [FilledButton(onPressed: _publish, child: const Text('Publish'))],
  );
}

Authors drag fields and containers from the palette, resize row cells and grid items on the canvas, edit properties in the inspector, and open Preview to fill in the form with FormView. Every change is one undo step.

Next steps ​