katana_form 2.14.1 copy "katana_form: ^2.14.1" to clipboard
katana_form: ^2.14.1 copied to clipboard

Package to provide FormController to define the use of forms and FormStyle to unify the look and feel of forms.

Masamune logo

Katana Form

Follow on Twitter Follow on Threads Maintained with Melos

GitHub Sponsor


[GitHub] | [YouTube] | [Packages] | [Twitter] | [Threads] | [LinkedIn] | [mathru.net]


Introduction #

Form implementation is a very important part of the application.

It has become an indispensable interface that allows users to enter their information into the application.

Simplifying the implementation of Forms can be very helpful in increasing the speed and safety of application implementation.

Flutter provides FormField-type widgets such as Form and TextFormField.

However, it does not address data handling, and data acquisition and storage require implementation for each state management system.

Also, although it is possible to change the design with InputDecoration, I would like to simplify it and use it like ButtonStyle because there are many setting items.

For this reason, I have created the following package.

  • Enables input/output using a form by storing values for use in the form in the FormController and passing them to the FormController.
  • Unify design specifications by making FormStyle available to all form widgets. Enables easy unification of design.

It can be easily written as follows.


final form = FormController(<String, dynamic>{});

return Scaffold(
  appBar: AppBar(title: const Text("App Demo")),
  body: ListView(
    padding: const EdgeInsets.symmetric(vertical: 32),
    children: [
      const FormLabel("Name"),
      FormTextField(
        form: form,
        initialValue: form.value["name"],
        onSaved: (value) => {...form.value, "name": value},
      ),
      const FormLabel("Description"),
      FormTextField(
        form: form,
        minLines: 5,
        initialValue: form.value["description"],
        onSaved: (value) => {...form.value, "description": value},
      ),
      FormButton(
        "Submit",
        icon: Icon(Icons.check),
        onPressed: () {
          final value = form.validate(); // Validate and get form values
          if (value == null) {
            return;
          }
          print(value);
          // Save value as is.
        },
      ),
    ]
  )
);

This package can also be used with freezed to write code more safely.

Installation #

Import the following packages

flutter pub add katana_form

Implementation #

Create a Controller #

First, define the FormController with initial values.

For new data creation, pass an empty object; for existing data, insert values read from the database.

This example is for a case where Map<String, dynamic> is used to handle data for a database.

// New data
final form = FormController(<String, dynamic>{});

// Existing data
final Map<String, dynamic> data = getRepositoryData();
final form = FormController(data);

This is maintained by a state management mechanism such as StatefulWidget.

Since FormController inherits from ChangeNotifier, it can be used in conjunction with riverpod's ChangeNotifierProvider, etc.

Form Implementation #

Form widget installation is not required.

All you have to do is pass the FormController you created, and if you pass a FormController, you must also pass onSaved. (If you want to use only onChanged, you do not need to pass FormController.)

Pass the initial value to initialValue. When passing an initial value, pass the value obtained from FormController.value as is.

onSaved is passed the currently entered value as a callback, so be sure to return the changed FormController.value value as is.

FormTextField(
  form: form,
  initialValue: form.value["description"],
  onSaved: (value) => {...form.value, "description": value},
),

Form Validation and Storage #

You can validate and save a form by executing FormController.validate.

Validation is performed first, and null is returned in case of failure.

If it succeeds, it returns the value changed by onSaved of each Form widget.

Update the database based on that value.

final value = form.validate(); // Validate and get form values
if (value == null) {
  return;
}
print(value);
// Save value as is.

Sample code #

The above sequence of events can be written in summary as follows

When destroying a form page, FormController should also be disposed of by disposing of it together with the form page.

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

  @override
  State<StatefulWidget> createState() => FormPageState();
}

class FormPageState extends State<FormPage> {
  final form = FormController(<String, dynamic>{});

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("App Demo")),
      body: ListView(
        padding: const EdgeInsets.symmetric(vertical: 32),
        children: [
          const FormLabel("Name"),
          FormTextField(
            form: form,
            initialValue: form.value["name"],
            onSaved: (value) => {...form.value, "name": value},
          ),
          const FormLabel("Description"),
          FormTextField(
            form: form,
            minLines: 5,
            initialValue: form.value["description"],
            onSaved: (value) => {...form.value, "description": value},
          ),
          const SizedBox(height: 16),
          FormButton(
            "Submit",
            icon: Icon(Icons.add),
            onPressed: () {
              final value = form.validate(); // Validate and get form values
              if (value == null) {
                return;
              }
              print(value);
              // Save value as is.
            },
          ),
        ],
      ),
    );
  }
}

Freezed allows you to write code more safely.

@freezed
class FormValue with _$FormValue {
  const factory FormValue({
    String? name,
    String? description,
  }) = _FormValue;
}

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

  @override
  State<StatefulWidget> createState() => FormPageState();
}

class FormPageState extends State<FormPage> {
  final form = FormController(FormValue());

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("App Demo")),
      body: ListView(
        padding: const EdgeInsets.symmetric(vertical: 32),
        children: [
          const FormLabel("Name"),
          FormTextField(
            form: form,
            initialValue: form.value.name,
            onSaved: (value) => form.value.copyWith(name: value),
          ),
          const FormLabel("Description"),
          FormTextField(
            form: form,
            minLines: 5,
            initialValue: form.value.description,
            onSaved: (value) => form.value.copyWith(description: value),
          ),
          const SizedBox(height: 16),
          FormButton(
            "Submit",
            icon: Icon(Icons.add),
            onPressed: () {
              final value = form.validate(); // Validate and get form values
              if (value == null) {
                return;
              }
              print(value);
              // Save value as is.
            },
          ),
        ],
      ),
    );
  }
}

Style Change #

The style of each FormWidget can be changed together with FormStyle.

The default style is plain, but if you specify the following, the style will be changed to the Material design with a border.

FormTextField(
  form: form,
  initialValue: form.value["name"],
  onSaved: (value) => {...form.value, "name": value},
  style: FormStyle(
      border: OutlineInputBorder(),
      padding: const EdgeInsets.symmetric(horizontal: 16),
      contentPadding: const EdgeInsets.all(16)),
),

Type of FormWidget #

Currently available FormWidget are

Adding as needed.

  • FormTextField
    • Field for entering text
  • FormDateTimeField
    • Field to select and enter the date and time in the Flutter dialog
  • FormDateField
    • Field to select the date (month and day) from a choice
  • FormNumField
    • Field to select a numerical value from a list of choices.
  • FormEnumField
    • Field to select from the Enum definition.
  • FormMapField
    • Field where you pass a Map and choose from its options.

The widgets that assist Form are as follows.

  • FormLabel
    • The label portion of the form is displayed separately. It also serves as a Divider.
  • FormButton
    • Used for confirm and cancel buttons for forms.
    • FormStyle is available.

GitHub Sponsors #

Sponsors are always welcome. Thank you for your support!

https://github.com/sponsors/mathrunet

1
likes
160
pub points
41%
popularity

Publisher

verified publishermathru.net

Package to provide FormController to define the use of forms and FormStyle to unify the look and feel of forms.

Homepage
Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter, flutter_rating_bar, katana, pin_input_text_field, universal_platform

More

Packages that depend on katana_form