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

An enhanced form builder with validation, conditional fields, and dynamic form generation for Flutter applications.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_form_builder_plus/flutter_form_builder_plus.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:form_builder_validators/form_builder_validators.dart';

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

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

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

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

  @override
  State<DemoHomePage> createState() => _DemoHomePageState();
}

class _DemoHomePageState extends State<DemoHomePage> {
  int _selectedIndex = 0;

  final List<Widget> _demos = [
    const BasicFormDemo(),
    const EnhancedValidatorsDemo(),
    const ConditionalFieldsDemo(),
    const DynamicFormDemo(),
    const AllFieldTypesDemo(),
  ];

  final List<String> _demoTitles = [
    'Basic Form',
    'Enhanced Validators',
    'Conditional Fields',
    'Dynamic Form',
    'All Field Types',
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter Form Builder Plus'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: Row(
        children: [
          NavigationRail(
            selectedIndex: _selectedIndex,
            onDestinationSelected: (index) {
              setState(() {
                _selectedIndex = index;
              });
            },
            labelType: NavigationRailLabelType.all,
            destinations: _demoTitles
                .map((title) => NavigationRailDestination(
                      icon: const Icon(Icons.circle_outlined),
                      selectedIcon: const Icon(Icons.circle),
                      label: Text(title),
                    ))
                .toList(),
          ),
          const VerticalDivider(thickness: 1, width: 1),
          Expanded(
            child: _demos[_selectedIndex],
          ),
        ],
      ),
    );
  }
}

// ==================== Basic Form Demo ====================
class BasicFormDemo extends StatefulWidget {
  const BasicFormDemo({super.key});

  @override
  State<BasicFormDemo> createState() => _BasicFormDemoState();
}

class _BasicFormDemoState extends State<BasicFormDemo> {
  final _formKey = GlobalKey<FormBuilderState>();
  final _formData = <String, dynamic>{};

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            'Basic Form with FormBuilderPlus',
            style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 8),
          const Text(
            'Demonstrates basic form building with FormBuilderPlus widget',
            style: TextStyle(color: Colors.grey),
          ),
          const SizedBox(height: 24),
          FormBuilderPlus(
            child: FormBuilder(
              key: _formKey,
              child: Column(
                children: [
                  FormBuilderTextField(
                    name: 'name',
                    decoration: const InputDecoration(
                      labelText: 'Name',
                      border: OutlineInputBorder(),
                      prefixIcon: Icon(Icons.person),
                    ),
                    validator: FormBuilderValidators.compose([
                      FormBuilderValidators.required(),
                      FormBuilderValidators.minLength(2),
                    ]),
                    onChanged: (value) {
                      setState(() {
                        _formData['name'] = value;
                      });
                    },
                  ),
                  const SizedBox(height: 16),
                  FormBuilderTextField(
                    name: 'email',
                    decoration: const InputDecoration(
                      labelText: 'Email',
                      border: OutlineInputBorder(),
                      prefixIcon: Icon(Icons.email),
                    ),
                    validator: FormBuilderValidators.compose([
                      FormBuilderValidators.required(),
                      FormBuilderValidators.email(),
                    ]),
                    onChanged: (value) {
                      setState(() {
                        _formData['email'] = value;
                      });
                    },
                  ),
                  const SizedBox(height: 24),
                  Row(
                    children: [
                      Expanded(
                        child: ElevatedButton(
                          onPressed: () {
                            if (_formKey.currentState?.saveAndValidate() ??
                                false) {
                              ScaffoldMessenger.of(context).showSnackBar(
                                SnackBar(
                                  content: Text('Form submitted: $_formData'),
                                  backgroundColor: Colors.green,
                                ),
                              );
                            }
                          },
                          child: const Text('Submit'),
                        ),
                      ),
                      const SizedBox(width: 16),
                      Expanded(
                        child: OutlinedButton(
                          onPressed: () {
                            _formKey.currentState?.reset();
                            setState(() {
                              _formData.clear();
                            });
                          },
                          child: const Text('Reset'),
                        ),
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

// ==================== Enhanced Validators Demo ====================
class EnhancedValidatorsDemo extends StatefulWidget {
  const EnhancedValidatorsDemo({super.key});

  @override
  State<EnhancedValidatorsDemo> createState() => _EnhancedValidatorsDemoState();
}

class _EnhancedValidatorsDemoState extends State<EnhancedValidatorsDemo> {
  final _formKey = GlobalKey<FormBuilderState>();

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            'Enhanced Validators',
            style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 8),
          const Text(
            'Demonstrates FormBuilderPlusValidators: alphanumeric, creditCard, postalCode, phoneNumber, strongPassword',
            style: TextStyle(color: Colors.grey),
          ),
          const SizedBox(height: 24),
          FormBuilder(
            key: _formKey,
            child: Column(
              children: [
                FormBuilderTextField(
                  name: 'username',
                  decoration: const InputDecoration(
                    labelText: 'Username (alphanumeric only)',
                    border: OutlineInputBorder(),
                    prefixIcon: Icon(Icons.account_circle),
                  ),
                  validator: FormBuilderValidators.compose([
                    FormBuilderValidators.required(),
                    FormBuilderPlusValidators.alphanumeric(),
                  ]),
                ),
                const SizedBox(height: 16),
                FormBuilderTextField(
                  name: 'creditCard',
                  decoration: const InputDecoration(
                    labelText: 'Credit Card Number',
                    border: OutlineInputBorder(),
                    prefixIcon: Icon(Icons.credit_card),
                  ),
                  validator: FormBuilderValidators.compose([
                    FormBuilderValidators.required(),
                    FormBuilderPlusValidators.creditCard(),
                  ]),
                ),
                const SizedBox(height: 16),
                FormBuilderTextField(
                  name: 'postalCode',
                  decoration: const InputDecoration(
                    labelText: 'Postal Code (US)',
                    border: OutlineInputBorder(),
                    prefixIcon: Icon(Icons.location_on),
                  ),
                  validator: FormBuilderValidators.compose([
                    FormBuilderValidators.required(),
                    FormBuilderPlusValidators.postalCode('US'),
                  ]),
                ),
                const SizedBox(height: 16),
                FormBuilderTextField(
                  name: 'phone',
                  decoration: const InputDecoration(
                    labelText: 'Phone Number (US)',
                    border: OutlineInputBorder(),
                    prefixIcon: Icon(Icons.phone),
                  ),
                  validator: FormBuilderValidators.compose([
                    FormBuilderValidators.required(),
                    FormBuilderPlusValidators.phoneNumber('US'),
                  ]),
                ),
                const SizedBox(height: 16),
                FormBuilderTextField(
                  name: 'password',
                  decoration: const InputDecoration(
                    labelText: 'Password (strong password)',
                    border: OutlineInputBorder(),
                    prefixIcon: Icon(Icons.lock),
                  ),
                  obscureText: true,
                  validator: FormBuilderValidators.compose([
                    FormBuilderValidators.required(),
                    FormBuilderPlusValidators.strongPassword(),
                  ]),
                ),
                const SizedBox(height: 24),
                ElevatedButton(
                  onPressed: () {
                    if (_formKey.currentState?.saveAndValidate() ?? false) {
                      ScaffoldMessenger.of(context).showSnackBar(
                        const SnackBar(
                          content: Text('All validations passed!'),
                          backgroundColor: Colors.green,
                        ),
                      );
                    }
                  },
                  child: const Text('Validate'),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

// ==================== Conditional Fields Demo ====================
class ConditionalFieldsDemo extends StatefulWidget {
  const ConditionalFieldsDemo({super.key});

  @override
  State<ConditionalFieldsDemo> createState() => _ConditionalFieldsDemoState();
}

class _ConditionalFieldsDemoState extends State<ConditionalFieldsDemo> {
  Map<String, dynamic> _formData = {}; // Store form data from FormBuilderPlus
  bool _hasAddress = false; // Track checkbox state separately
  String? _userType; // Track user type separately

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            'Conditional Fields',
            style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 8),
          const Text(
            'Fields that show/hide based on other field values using ConditionalFormField.create()',
            style: TextStyle(color: Colors.grey),
          ),
          const SizedBox(height: 24),
          Builder(
            builder: (context) {
              return FormBuilderPlus(
                onChanged: (formData) {
                  // Store the latest form data
                  setState(() {
                    _formData = formData;
                  });
                },
                child: Column(
                  children: [
                    FormBuilderDropdown<String>(
                      name: 'userType',
                      decoration: const InputDecoration(
                        labelText: 'User Type',
                        border: OutlineInputBorder(),
                        prefixIcon: Icon(Icons.person),
                      ),
                      items: const [
                        DropdownMenuItem(
                            value: 'individual', child: Text('Individual')),
                        DropdownMenuItem(
                            value: 'business', child: Text('Business')),
                        DropdownMenuItem(
                            value: 'organization', child: Text('Organization')),
                      ],
                      onChanged: (value) {
                        // Update local state for conditional rendering
                        setState(() {
                          _userType = value;
                        });
                      },
                    ),
                    const SizedBox(height: 16),
                    // Conditional field: Company Name (shows when userType is 'business')
                    if (_userType == 'business')
                      FormBuilderTextField(
                        name: 'companyName',
                        key: ValueKey(
                            'companyName_$_userType'), // Force rebuild when value changes
                        decoration: const InputDecoration(
                          labelText: 'Company Name',
                          border: OutlineInputBorder(),
                          prefixIcon: Icon(Icons.business),
                        ),
                      ),
                    // Conditional field: Organization Name (shows when userType is 'organization')
                    if (_userType == 'organization')
                      FormBuilderTextField(
                        name: 'organizationName',
                        key: ValueKey(
                            'organizationName_$_userType'), // Force rebuild when value changes
                        decoration: const InputDecoration(
                          labelText: 'Organization Name',
                          border: OutlineInputBorder(),
                          prefixIcon: Icon(Icons.corporate_fare),
                        ),
                      ),
                    const SizedBox(height: 16),
                    FormBuilderCheckbox(
                      name: 'hasAddress',
                      title: const Text('I have a shipping address'),
                      onChanged: (value) {
                        // Update local state for conditional rendering
                        setState(() {
                          _hasAddress = value ?? false;
                        });
                      },
                    ),
                    const SizedBox(height: 16),
                    // Conditional field: Address (shows when hasAddress is true)
                    // Use the tracked _hasAddress state directly
                    if (_hasAddress)
                      FormBuilderTextField(
                        name: 'address',
                        key: ValueKey(
                            'address_$_hasAddress'), // Force rebuild when value changes
                        decoration: const InputDecoration(
                          labelText: 'Shipping Address',
                          border: OutlineInputBorder(),
                          prefixIcon: Icon(Icons.location_on),
                        ),
                        maxLines: 4,
                      ),
                    const SizedBox(height: 24),
                    Row(
                      children: [
                        Expanded(
                          child: Builder(
                            builder: (buttonContext) {
                              return ElevatedButton(
                                onPressed: () {
                                  // Save the form first to ensure all values are captured
                                  buttonContext.saveForm();
                                  // Get form data directly from FormBuilderPlus using context extension
                                  final formData =
                                      buttonContext.formData ?? _formData;
                                  final hasAddressValue =
                                      formData['hasAddress'];
                                  ScaffoldMessenger.of(context).showSnackBar(
                                    SnackBar(
                                      content: Text(
                                        'Form data: $formData\n'
                                        'hasAddress: $hasAddressValue (${hasAddressValue.runtimeType})\n'
                                        'Equals true?: ${hasAddressValue == true}',
                                      ),
                                      backgroundColor: Colors.blue,
                                      duration: const Duration(seconds: 5),
                                    ),
                                  );
                                },
                                child: const Text('Show Form Data'),
                              );
                            },
                          ),
                        ),
                      ],
                    ),
                  ],
                ),
              );
            },
          ),
        ],
      ),
    );
  }
}

// ==================== Dynamic Form Demo ====================
class DynamicFormDemo extends StatefulWidget {
  const DynamicFormDemo({super.key});

  @override
  State<DynamicFormDemo> createState() => _DynamicFormDemoState();
}

class _DynamicFormDemoState extends State<DynamicFormDemo> {
  final _formData = <String, dynamic>{};

  @override
  Widget build(BuildContext context) {
    final config = {
      'name': 'user_registration',
      'fields': [
        {
          'name': 'username',
          'type': 'text',
          'label': 'Username',
          'required': true,
          'placeholder': 'Enter your username',
        },
        {
          'name': 'email',
          'type': 'email',
          'label': 'Email',
          'required': true,
          'placeholder': 'Enter your email',
        },
        {
          'name': 'age',
          'type': 'number',
          'label': 'Age',
          'required': true,
          'placeholder': 'Enter your age',
        },
        {
          'name': 'country',
          'type': 'dropdown',
          'label': 'Country',
          'options': {
            'choices': [
              'United States',
              'United Kingdom',
              'Canada',
              'Australia',
              'Germany'
            ],
          },
        },
        {
          'name': 'bio',
          'type': 'textarea',
          'label': 'Bio',
          'placeholder': 'Tell us about yourself',
          'options': {
            'maxLines': 4,
          },
        },
        {
          'name': 'agreeToTerms',
          'type': 'checkbox',
          'label': 'I agree to the terms and conditions',
          'required': true,
        },
      ],
      'options': {
        'autoValidate': false,
        'skipDisabled': true,
        'enabled': true,
      },
    };

    return SingleChildScrollView(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            'Dynamic Form Generation',
            style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 8),
          const Text(
            'Forms generated from configuration objects. Supports all field types and conditional logic.',
            style: TextStyle(color: Colors.grey),
          ),
          const SizedBox(height: 24),
          DynamicForm.create(
            config: config,
            formData: _formData,
            onFieldChanged: (name, value) {
              setState(() {
                _formData[name] = value;
              });
            },
            onFormSubmitted: (data) {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(
                  content: Text('Form submitted: $data'),
                  backgroundColor: Colors.green,
                ),
              );
            },
            onFormReset: () {
              setState(() {
                _formData.clear();
              });
            },
          ),
        ],
      ),
    );
  }
}

// ==================== All Field Types Demo ====================
class AllFieldTypesDemo extends StatefulWidget {
  const AllFieldTypesDemo({super.key});

  @override
  State<AllFieldTypesDemo> createState() => _AllFieldTypesDemoState();
}

class _AllFieldTypesDemoState extends State<AllFieldTypesDemo> {
  final _formData = <String, dynamic>{};

  @override
  Widget build(BuildContext context) {
    final config = {
      'name': 'all_field_types',
      'fields': [
        {
          'name': 'textField',
          'type': 'text',
          'label': 'Text Field',
          'placeholder': 'Enter text',
          'required': true,
        },
        {
          'name': 'emailField',
          'type': 'email',
          'label': 'Email Field',
          'placeholder': 'Enter email',
          'required': true,
        },
        {
          'name': 'passwordField',
          'type': 'password',
          'label': 'Password Field',
          'placeholder': 'Enter password',
          'required': true,
        },
        {
          'name': 'numberField',
          'type': 'number',
          'label': 'Number Field',
          'placeholder': 'Enter number',
          'required': true,
        },
        {
          'name': 'textareaField',
          'type': 'textarea',
          'label': 'Textarea Field',
          'placeholder': 'Enter multi-line text',
          'options': {
            'maxLines': 5,
          },
        },
        {
          'name': 'checkboxField',
          'type': 'checkbox',
          'label': 'Checkbox Field',
        },
        {
          'name': 'radioField',
          'type': 'radio',
          'label': 'Radio Field',
          'options': {
            'choices': ['Option 1', 'Option 2', 'Option 3'],
          },
        },
        {
          'name': 'dropdownField',
          'type': 'dropdown',
          'label': 'Dropdown Field',
          'options': {
            'choices': ['Choice 1', 'Choice 2', 'Choice 3', 'Choice 4'],
          },
        },
      ],
      'options': {
        'autoValidate': false,
        'enabled': true,
      },
    };

    return SingleChildScrollView(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            'All Field Types',
            style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 8),
          const Text(
            'Demonstrates all supported field types: text, email, password, number, textarea, checkbox, radio, dropdown',
            style: TextStyle(color: Colors.grey),
          ),
          const SizedBox(height: 24),
          DynamicForm.create(
            config: config,
            formData: _formData,
            onFieldChanged: (name, value) {
              setState(() {
                _formData[name] = value;
              });
            },
            onFormSubmitted: (data) {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(
                  content: Text('All field types demo submitted: $data'),
                  backgroundColor: Colors.green,
                  duration: const Duration(seconds: 3),
                ),
              );
            },
            onFormReset: () {
              setState(() {
                _formData.clear();
              });
            },
          ),
        ],
      ),
    );
  }
}
2
likes
160
points
166
downloads

Publisher

verified publisherbechattaoui.dev

Weekly Downloads

An enhanced form builder with validation, conditional fields, and dynamic form generation for Flutter applications.

Repository (GitHub)
View/report issues

Topics

#form #validation #flutter #widget #dynamic-forms

Documentation

API reference

Funding

Consider supporting this project:

github.com

License

MIT (license)

Dependencies

flutter, flutter_form_builder, form_builder_validators

More

Packages that depend on flutter_form_builder_plus