ezy_form 0.0.1 copy "ezy_form: ^0.0.1" to clipboard
ezy_form: ^0.0.1 copied to clipboard

Easy to handle form in Flutter without any complexity. Easy to use and customize.

example/lib/main.dart

import 'package:ezy_form/ezy_form.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const DynamicFormExample(),
    );
  }
}

final form = FormGroup({
  'name': FormControl<String>(null, validators: [requiredValidator]),
  'email': FormControl<String>(null, validators: [requiredValidator]),
  'gender': FormControl<String>(null, validators: [requiredValidator]),
  'agreed': FormControl<bool>(false, validators: [requiredTrueValidator]),
  'tags': FormArrayControl<String>(null, validators: [requiredValidator]),
  'info': FormGroup({
    'firstName':
        FormControl<String>("fistname", validators: [requiredValidator]),
    'lastName': FormControl<String>('last', validators: [requiredValidator]),
  }),
});

class DynamicFormExample extends StatelessWidget {
  const DynamicFormExample({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Dynamic Form Example')),
      body: SingleChildScrollView(
        child: EzyFormWidget(
          formGroup: form,
          builder: (context, model) => Column(
            children: [
              EzyFormControl<String>(
                formControlName: 'info.firstName',
                builder: (context, control) => ControlledTextField(
                  onTextChanged: (value) => control.setValue(value),
                  onFormControlReset: (fn) => control.onReset(fn),
                  onDirty: () => control.markAsDirty(),
                  onTouched: () => control.markAsTouched(),
                  decoration: InputDecoration(
                    labelText: 'First Name',
                    errorText: control.valid ? null : 'First name is required',
                    helperText: control.dirty
                        ? 'First name is dirty'
                        : control.touched
                            ? 'First name is touched'
                            : null,
                  ),
                ),
              ),
              EzyFormControl<String>(
                formControlName: 'name',
                builder: (context, control) => ControlledTextField(
                  onTextChanged: (value) => control.setValue(value),
                  onFormControlReset: (fn) => control.onReset(fn),
                  onDirty: () => control.markAsDirty(),
                  onTouched: () => control.markAsTouched(),
                  decoration: InputDecoration(
                    labelText: 'Name',
                    errorText: control.valid ? null : 'Name is required',
                    helperText: control.dirty
                        ? 'Name is dirty'
                        : control.touched
                            ? 'Name is touched'
                            : null,
                  ),
                ),
              ),
              EzyFormControl<String>(
                formControlName: 'email',
                builder: (context, control) => TextField(
                  onChanged: (value) => control.setValue(value),
                  decoration: InputDecoration(
                    labelText: 'Email',
                    errorText: control.valid ? null : 'Email is required',
                  ),
                ),
              ),
              EzyFormControl<String>(
                formControlName: 'gender',
                builder: (context, control) => DropdownButtonFormField<String>(
                  value: control.value,
                  onChanged: (value) => control.setValue(value),
                  decoration: InputDecoration(
                    labelText: 'Gender',
                    errorText: control.valid ? null : 'Gender is required',
                  ),
                  items: const [
                    DropdownMenuItem(value: 'male', child: Text('Male')),
                    DropdownMenuItem(value: 'female', child: Text('Female')),
                  ],
                ),
              ),
              EzyFormArrayControl<String>(
                formControlName: 'tags',
                builder: (context, arrayControl) => Column(
                  mainAxisSize: MainAxisSize.min,
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Row(
                      children: [
                        Text('Tags:'),
                        TextButton.icon(
                            onPressed: () => arrayControl.add(),
                            icon: Icon(Icons.add),
                            label: Text('Add Tag')),
                      ],
                    ),
                    Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      mainAxisSize: MainAxisSize.min,
                      children: (arrayControl.controls ?? [])
                          .mapIndexed((control, index) => TextFormField(
                                key: ValueKey(control.hashCode + index),
                                initialValue: control.value,
                                decoration: InputDecoration(
                                    labelText: 'Tag $index',
                                    errorText: control.valid
                                        ? null
                                        : 'Tag $index is required',
                                    suffixIcon: IconButton(
                                      icon: Icon(Icons.remove),
                                      onPressed: () =>
                                          arrayControl.remove(index),
                                    )),
                                onChanged: (value) => control.setValue(value),
                              ))
                          .toList(),
                    ),
                  ],
                ),
              ),
              EzyFormControl<bool>(
                formControlName: 'agreed',
                builder: (context, control) => Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    CheckboxListTile(
                      value: control.value ?? false,
                      onChanged: (value) => control.setValue(value),
                      title: const Text('I agree'),
                    ),
                    if (!control.valid)
                      const Text(
                        'You must agree to the terms and conditions',
                        style: TextStyle(color: Colors.red),
                      ),
                  ],
                ),
              ),
              const SizedBox(height: 20),
              ElevatedButton(
                onPressed: () {
                  model.validate();
                  print(model.values);
                },
                child: const Text('Submit'),
              ),
              const SizedBox(height: 20),
              ElevatedButton(
                onPressed: () {
                  model.reset();
                  print(model.values);
                },
                child: const Text('reset'),
              )
            ],
          ),
        ),
      ),
    );
  }
}

class ControlledTextField extends HookWidget {
  const ControlledTextField({
    super.key,
    this.decoration,
    this.onTextChanged,
    this.onFormControlReset,
    this.onTouched,
    this.onDirty,
  });

  final InputDecoration? decoration;
  final Function(String value)? onTextChanged;
  final Function(VoidCallback fn)? onFormControlReset;
  final VoidCallback? onTouched;
  final VoidCallback? onDirty;

  @override
  Widget build(BuildContext context) {
    return HookBuilder(builder: (context) {
      final textCtrl = useTextEditingController();

      final focusNode = useFocusNode();

      useEffect(() {
        onFormControlReset?.call(textCtrl.clear);

        focusNode.addListener(() {
          if (focusNode.hasFocus) {
            onTouched?.call();
          }
        });

        textCtrl.addListener(() {
          if (textCtrl.text != '') {
            onDirty?.call();
          }

          onTextChanged?.call(textCtrl.text);
        });
        return null;
      }, []);
      return TextField(
        focusNode: focusNode,
        controller: textCtrl,
        decoration: decoration,
      );
    });
  }
}

extension IndexedIterable<E> on Iterable<E> {
  Iterable<T> mapIndexed<T>(T Function(E e, int i) f) {
    var i = 0;
    return map((e) => f(e, i++));
  }
}
3
likes
160
pub points
0%
popularity

Publisher

verified publishersamderlust.com

Easy to handle form in Flutter without any complexity. Easy to use and customize.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (LICENSE)

Dependencies

flutter

More

Packages that depend on ezy_form