form_wizard 0.1.2 copy "form_wizard: ^0.1.2" to clipboard
form_wizard: ^0.1.2 copied to clipboard

The most performant, customizable, and lightweight form validation and builder package for Flutter.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:form_wizard/form_wizard.dart';
import 'package:intl/intl.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'FormWizard Demo',
      theme: ThemeData(primarySwatch: Colors.teal),
      home: const DemoShell(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          title: const Text('FormWizard Demo'),
          bottom: const TabBar(
            tabs: [
              Tab(text: 'Form'),
              Tab(text: 'Stepper'),
              Tab(text: 'Templates'),
            ],
          ),
        ),
        body: const TabBarView(
          children: [
            ExampleFormPage(),
            FormStepperWidget(),
            TemplatesShowcase(),
          ],
        ),
      ),
    );
  }
}

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

  @override
  State<ExampleFormPage> createState() => _ExampleFormPageState();
}

class _ExampleFormPageState extends State<ExampleFormPage> {
  final FormWizardController _controller = FormWizardController();

  final List<FormWizardFieldModel> _fields = [
    FormWizardFieldPresets.nameField(label: 'Full Name', required: true),

    FormWizardFieldPresets.emailField(label: 'Email Address', required: true),
    FormWizardFieldPresets.passwordField(label: 'Password', required: true),
    FormWizardFieldPresets.countryDropdown(
      label: 'Country',
      required: true,
      countries: const [
        'USA',
        'Australia',
        'Brazil',
        'Canada',
        'France',
        'Germany',
        'India',
        'Japan',
        'United Kingdom',
        'United States',
      ],
    ),
    FormWizardFieldModel(
      name: 'state',
      label: 'State',
      type: FieldType.text,
      hint: 'Required only for USA',
      validators: [Validators.required()],
      visibleWhenDependsOn: const ['country'],
      visibleWhen: (values) => values['country'] == 'USA',
      decorationBuilder:
          (errorText, controller) => InputDecoration(
            prefixIcon: const Icon(Icons.map),
            labelText: 'State',
            errorText: errorText,
            border: const OutlineInputBorder(
              borderRadius: BorderRadius.all(Radius.circular(12)),
            ),
          ),
    ),

    FormWizardFieldModel(
      name: 'dob',
      label: 'Date of Birth',
      type: FieldType.date,
      validators: [Validators.required()],
      dateFormatter: (date) => DateFormat('dd MMM yyyy').format(date),
      decorationBuilder: (errorText, controller) {
        return InputDecoration(
          prefixIcon: const Icon(Icons.calendar_month),
          labelText: 'Date of Birth',
          errorText: errorText,
          border: const OutlineInputBorder(
            borderRadius: BorderRadius.all(Radius.circular(12)),
          ),
        );
      },
    ),

    FormWizardFieldModel(
      name: 'number',
      label: 'Number',
      type: FieldType.number,
      validators: [Validators.required(), Validators.number()],
      decorationBuilder:
          (errorText, controller) => InputDecoration(
            prefixIcon: const Icon(Icons.phone),
            labelText: 'Number',
            errorText: errorText,
            border: const OutlineInputBorder(
              borderRadius: BorderRadius.all(Radius.circular(12)),
            ),
          ),
    ),
    FormWizardFieldModel(
      name: 'custom_slider',
      label: 'Custom Field',
      type: FieldType.custom,
      customBuilder: (controller, errorText, onChanged) {
        double value = double.tryParse(controller.text) ?? 0.0;
        return Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text("Select value"),
            Slider(
              min: 0,
              max: 100,
              divisions: 100,
              value: value,
              onChanged: (val) {
                controller.text = val.toString();
                onChanged(val.toString());
              },
            ),
            if (errorText != null)
              Padding(
                padding: const EdgeInsets.only(left: 8.0, top: 4),
                child: Text(
                  errorText,
                  style: const TextStyle(color: Colors.red),
                ),
              ),
          ],
        );
      },
      validators: [
        (val) {
          final v = double.tryParse(val ?? '');
          if (v == null || v < 20) return "Value must be at least 20";
          return null;
        },
      ],
    ),
  ];

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

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: SingleChildScrollView(
        child: FormWizard(
          fields: _fields,
          fieldArrays: [
            FormWizardFieldArrayModel(
              name: 'phones',
              label: 'Phone Numbers',
              initialItemCount: 1,
              minItems: 1,
              maxItems: 4,
              fieldBuilder: (item) {
                return [
                  FormWizardFieldModel(
                    name: item.fieldName('number'),
                    label: 'Phone ${item.index + 1}',
                    type: FieldType.number,
                    validators: [Validators.required(), Validators.number()],
                    decorationBuilder:
                        (errorText, controller) => InputDecoration(
                          prefixIcon: const Icon(Icons.phone),
                          labelText: 'Phone ${item.index + 1}',
                          errorText: errorText,
                          border: const OutlineInputBorder(
                            borderRadius: BorderRadius.all(Radius.circular(12)),
                          ),
                        ),
                  ),
                ];
              },
            ),
          ],
          controller: _controller,
          onSubmit: (values) {
            showDialog(
              context: context,
              builder:
                  (_) => AlertDialog(
                    title: const Text('Form Submitted'),
                    content: Text(values.toString()),
                  ),
            );
          },
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return LoginForm(
      onLogin: (name, password) {
        showDialog(
          context: context,
          builder:
              (_) => AlertDialog(
                title: const Text('Login Submitted'),
                content: Text('Identity: $name\nPassword: $password'),
              ),
        );
      },
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: FormWizardStepper(
        steps: [
          FormWizardStep(
            title: 'Personal',
            fields: [FormWizard.nameField(name: 'name')],
          ),
          FormWizardStep(
            title: 'Contact',
            fields: [FormWizard.emailField(name: 'email')],
          ),
          FormWizardStep(
            title: 'Address',
            fields: [
              FormWizard.streetField(),
              FormWizard.cityField(),
              FormWizard.zipField(),
            ],
          ),
        ],
        onStepChanged: (_) {},
        onFinish: (values) {
          showDialog(
            context: context,
            builder:
                (_) => AlertDialog(
                  title: const Text('Stepper Submitted'),
                  content: Text(values.toString()),
                ),
          );
        },
        stepBuilder: (context, stepper) {
          return Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              LinearProgressIndicator(
                value: (stepper.currentStep + 1) / stepper.steps.length,
              ),
              const SizedBox(height: 16),
              Text(
                stepper.steps[stepper.currentStep].title,
                style: Theme.of(context).textTheme.headlineSmall,
              ),
              const SizedBox(height: 12),
              stepper.fields,
              const SizedBox(height: 16),
              Row(
                children: [
                  TextButton(
                    onPressed: stepper.onBack,
                    child: const Text('Back'),
                  ),
                  const Spacer(),
                  FilledButton(
                    key: const ValueKey<String>('next-step'),
                    onPressed: stepper.onNext,
                    child: Text(
                      stepper.currentStep == stepper.steps.length - 1
                          ? 'Finish'
                          : 'Next',
                    ),
                  ),
                ],
              ),
            ],
          );
        },
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return ListView(
      padding: const EdgeInsets.all(16),
      children: [
        _TemplatePanel(
          title: 'LoginForm',
          child: LoginForm(
            rememberMe: true,
            forgotPasswordLink: () => _show(context, 'Forgot password tapped'),
            onLogin:
                (identity, password) =>
                    _show(context, 'Login: $identity / $password'),
          ),
        ),
        _TemplatePanel(
          title: 'SignupForm',
          child: SignupForm(
            requireTermsAcceptance: true,
            onSignup:
                (name, identity, password) =>
                    _show(context, 'Signup: $name / $identity'),
          ),
        ),
        _TemplatePanel(
          title: 'OTPVerificationForm',
          child: OTPVerificationForm(
            otpLength: 4,
            resendCooldownSeconds: 10,
            onVerify: (otp) => _show(context, 'OTP: $otp'),
            onResend: () => _show(context, 'OTP resent'),
          ),
        ),
        _TemplatePanel(
          title: 'AddressForm',
          child: AddressForm(
            onSubmit: (address) => _show(context, address.toString()),
          ),
        ),
        _TemplatePanel(
          title: 'PaymentForm',
          child: PaymentForm(
            submitLabel: 'Pay Now',
            onSubmit: (payment) => _show(context, payment.toString()),
          ),
        ),
      ],
    );
  }

  static void _show(BuildContext context, String message) {
    showDialog(
      context: context,
      builder:
          (_) => AlertDialog(
            title: const Text('Template Submitted'),
            content: Text(message),
          ),
    );
  }
}

class _TemplatePanel extends StatelessWidget {
  const _TemplatePanel({required this.title, required this.child});

  final String title;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return ExpansionTile(
      title: Text(title),
      children: [
        Padding(padding: const EdgeInsets.only(bottom: 16), child: child),
      ],
    );
  }
}
3
likes
160
points
241
downloads

Documentation

API reference

Publisher

verified publishershyphoton.in

Weekly Downloads

The most performant, customizable, and lightweight form validation and builder package for Flutter.

Repository (GitHub)
View/report issues

Topics

#form #validation #flutter-form #input #smart-form

License

MIT (license)

Dependencies

flutter, flutter_riverpod, intl

More

Packages that depend on form_wizard