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

Production-ready form building with validation, multi-step wizards, and draft persistence for the Fifty Flutter Kit.

Fifty Forms #

Production-ready form building with validation, multi-step wizards, and draft persistence. Part of Fifty Flutter Kit.

Home Login Form Registration Multi-Step

Features #

  • State Management - FiftyFormController for centralized form state
  • Immutable Field State - FieldState tracks value, touched, dirty, error states
  • 25 Built-in Validators - Required, Email, MinLength, Pattern, and more
  • Async Validation - Debounced async validators for server-side checks
  • FDL Components - Form field wrappers for fifty_ui components
  • Multi-Step Forms - Wizard-style forms with step validation
  • Dynamic Arrays - Add/remove repeating field groups
  • Draft Persistence - Auto-save and restore form data

Installation #

Add to your pubspec.yaml:

dependencies:
  fifty_forms:
    path: ../fifty_forms

Quick Start #

import 'package:fifty_forms/fifty_forms.dart';

final controller = FiftyFormController(
  initialValues: {'email': '', 'password': ''},
  validators: {
    'email': [Required(), Email()],
    'password': [Required(), MinLength(8)],
  },
);

Column(
  children: [
    FiftyTextFormField(
      name: 'email',
      controller: controller,
      label: 'Email',
      keyboardType: TextInputType.emailAddress,
    ),
    FiftyTextFormField(
      name: 'password',
      controller: controller,
      label: 'Password',
      obscureText: true,
    ),
    FiftySubmitButton(
      controller: controller,
      label: 'LOGIN',
      onPressed: () => controller.submit((values) async {
        await api.login(values['email'], values['password']);
      }),
    ),
  ],
)

Architecture #

fifty_forms
├── core/
│   ├── FiftyFormController   # Central state manager
│   └── FieldState            # Immutable per-field state
├── validators/
│   ├── Validator             # Sync validator base
│   ├── AsyncValidator        # Async validator with debounce
│   └── Built-ins             # Required, Email, MinLength, etc.
├── fields/
│   └── FiftyTextFormField    # fifty_ui field wrappers
│   └── FiftyDropdownFormField
│   └── FiftyCheckboxFormField (+ others)
├── widgets/
│   ├── FiftyForm             # Form container
│   ├── FiftySubmitButton     # Submit with loading state
│   ├── FiftyMultiStepForm    # Wizard container
│   ├── FiftyFormArray        # Dynamic repeating fields
│   └── FiftyValidationSummary
├── models/
│   ├── FormStep              # Step definition for wizards
│   └── FormStatus            # Form lifecycle enum
└── persistence/
    └── DraftManager          # Auto-save via GetStorage

Core Components #

Component Description
FiftyFormController Central state manager: values, validation, submission
FieldState<T> Immutable container for a single field's state
FormStatus Enum: idle, validating, submitting, submitted, error
Validator Composable synchronous validator base class
AsyncValidator Asynchronous validator with debounce support
DraftManager Persists and restores form drafts via GetStorage
FiftyMultiStepForm Wizard-style multi-step form widget
FiftyFormArray Dynamic add/remove repeating field groups

API Reference #

FiftyFormController #

The central state manager for forms. Handles field registration, value tracking, validation (sync and async), and form submission.

final controller = FiftyFormController(
  initialValues: {'name': '', 'age': 0},
  validators: {
    'name': [Required(), MinLength(2)],
    'age': [Required(), Min(18)],
  },
  onValidationChanged: (isValid) => print('Valid: $isValid'),
);

// Get/set values
controller.setValue('name', 'John');
final name = controller.getValue<String>('name');

// Check state
controller.isValid;      // All fields valid?
controller.isDirty;      // Any field changed?
controller.isValidating; // Async validation running?

// Field operations
controller.registerField('newField', initialValue: '');
controller.unregisterField('fieldToRemove');
controller.markTouched('name');
controller.markAllTouched();

// Actions
await controller.validate();          // Validate all fields
await controller.validateField('email'); // Validate single field
controller.clearErrors();             // Clear all validation errors
controller.reset();                   // Reset to initial values
controller.clear();                   // Clear all values

// Submit
await controller.submit((values) async {
  await api.save(values);
});

// Cleanup
controller.dispose();

Key Properties:

Property Type Description
status FormStatus Current form lifecycle status
isValid bool All fields valid and not validating
isDirty bool Any field changed from initial value
isValidating bool Async validation in progress
values Map<String, dynamic> All current field values
errors Map<String, String> All fields with errors
fieldNames List<String> All registered field names

FieldState #

Immutable state container for a form field. Tracks the current value, validation error, and interaction state.

class FieldState<T> {
  final T? value;           // Current value
  final String? error;      // Validation error
  final bool isTouched;     // Field has been focused
  final bool isDirty;       // Value differs from initial
  final bool isValidating;  // Async validation running

  bool get isValid => error == null && !isValidating;
  bool get hasError => error != null;
}

// Usage
final state = controller.getFieldState('email');
if (state.isTouched && state.hasError) {
  print(state.error);
}

FormStatus #

Enum representing the form lifecycle status:

Status Description
idle Default state, ready for input
validating Validation in progress
submitting Form submission in progress
submitted Form successfully submitted
error Validation or submission failed

Validators #

String Validators:

Validator Description Example
Required() Non-null, non-empty Required(message: 'Required')
MinLength(n) Minimum length MinLength(8)
MaxLength(n) Maximum length MaxLength(100)
Email() Valid email format Email()
Url() Valid URL format Url()
Pattern(regex) Matches pattern Pattern(RegExp(r'^[A-Z]+$'))
AlphaNumeric() Letters and numbers only AlphaNumeric()

Number Validators:

Validator Description Example
Min(n) Minimum value Min(0)
Max(n) Maximum value Max(100)
Range(min, max) Within range (inclusive) Range(1, 10)
Integer() Must be integer Integer()
Positive() Must be positive (> 0) Positive()

Date Validators:

Validator Description Example
MinDate(date) On or after date MinDate(DateTime.now())
MaxDate(date) On or before date MaxDate(DateTime(2030))
MinAge(years) Minimum age MinAge(18)
FutureDate() Must be future FutureDate()
PastDate() Must be past PastDate()

Password Validators:

Validator Description Example
HasUppercase() Contains uppercase HasUppercase()
HasLowercase() Contains lowercase HasLowercase()
HasNumber() Contains digit HasNumber()
HasSpecialChar() Contains special char HasSpecialChar()

Comparison Validators:

Validator Description Example
Equals(field) Equals another field Equals('password')
NotEquals(field) Differs from field NotEquals('oldPassword')

Form Fields #

Wrapper components for fifty_ui widgets that integrate with FiftyFormController:

Field Wraps Use Case
FiftyTextFormField FiftyTextField Text input
FiftyDropdownFormField FiftyDropdown Selection from list
FiftyCheckboxFormField FiftyCheckbox Boolean toggle
FiftySwitchFormField FiftySwitch On/off toggle
FiftyRadioFormField FiftyRadioGroup Single selection from options
FiftySliderFormField FiftySlider Numeric range selection
FiftyDateFormField Date picker Date input
FiftyTimeFormField Time picker Time input
FiftyFileFormField File picker File upload

UI Widgets #

Widget Description
FiftyForm Form container with controller binding
FiftySubmitButton Submit button with loading state
FiftyFormProgress Step progress indicator
FiftyMultiStepForm Multi-step wizard container
FiftyFormArray Dynamic repeating fields
FiftyFormError Form-level error display
FiftyFieldError Field-level error display
FiftyValidationSummary All errors summary
FiftyFormField Generic field wrapper

Usage Patterns #

Strong Password Validation #

validators: {
  'password': [
    Required(message: 'Password is required'),
    MinLength(8, message: 'At least 8 characters'),
    HasUppercase(message: 'Must contain uppercase letter'),
    HasLowercase(message: 'Must contain lowercase letter'),
    HasNumber(message: 'Must contain a number'),
    HasSpecialChar(message: 'Must contain special character'),
  ],
}

Password Confirmation #

validators: {
  'password': [Required(), MinLength(8)],
  'confirmPassword': [Required(), Equals('password', message: 'Passwords must match')],
}

Custom Validators #

// Synchronous
Custom<String>((value) {
  if (value?.contains('banned') == true) {
    return 'Contains banned word';
  }
  return null;
})

// Asynchronous with debounce
AsyncCustom<String>(
  (value) async {
    final exists = await api.checkUsername(value);
    return exists ? 'Username taken' : null;
  },
  debounce: Duration(milliseconds: 500),
)

Composite Validators #

// And — all must pass
And([Required(), MinLength(8), HasUppercase()])

// Or — at least one must pass
Or([Email(), Pattern(phoneRegex)])

Multi-Step Form #

FiftyMultiStepForm(
  controller: controller,
  steps: [
    FormStep(
      title: 'Account',
      description: 'Create your credentials',
      fields: ['email', 'password'],
    ),
    FormStep(
      title: 'Profile',
      fields: ['name', 'bio'],
      isOptional: true,
    ),
    FormStep(
      title: 'Review',
      fields: [],
      validator: (values) {
        if (values['bio']?.isEmpty == true) {
          return 'Consider adding a bio';
        }
        return null;
      },
    ),
  ],
  stepBuilder: (context, index, step) => _buildStep(index),
  onComplete: (values) => api.createUser(values),
  onStepChanged: (step) => print('Now on step $step'),
  showProgress: true,
  validateOnNext: true,
  nextLabel: 'NEXT',
  previousLabel: 'BACK',
  completeLabel: 'COMPLETE',
)

FormStep Properties:

Property Type Description
title String Step title for progress indicator
description String? Optional step description
fields List<String> Field names to validate for this step
isOptional bool Can skip if all fields empty
validator Function? Custom step-level validation

Dynamic Form Arrays #

FiftyFormArray(
  controller: controller,
  name: 'addresses',
  minItems: 1,
  maxItems: 5,
  itemBuilder: (context, index, remove) => Column(
    children: [
      FiftyTextFormField(
        name: 'addresses[$index].street',
        controller: controller,
        label: 'Street',
      ),
      FiftyTextFormField(
        name: 'addresses[$index].city',
        controller: controller,
        label: 'City',
      ),
      IconButton(
        icon: Icon(Icons.delete),
        onPressed: remove,
      ),
    ],
  ),
  addButtonBuilder: (add) => FiftyButton(
    label: 'Add Address',
    onPressed: add,
    variant: FiftyButtonVariant.ghost,
    icon: Icons.add,
  ),
)

Accessing Array Values:

// Get single field
final street = controller.getArrayValue<String>('addresses', 0, 'street');

// Set single field
controller.setArrayValue('addresses', 0, 'city', 'New York');

// Get all items as list of maps
final addresses = controller.getArrayValues('addresses');
// Returns: [{'street': '123 Main', 'city': 'NYC'}, ...]

// Get array length
final count = controller.getArrayLength('addresses');

// Remove item (shifts subsequent indices)
controller.removeArrayItem('addresses', 1);

FiftyFormArray Properties:

Property Type Default Description
name String required Base name for array fields
minItems int 0 Minimum items required
maxItems int 10 Maximum items allowed
initialCount int 1 Initial number of items
animate bool true Animate add/remove
itemSpacing double 16 Spacing between items

Draft Persistence #

// Initialize storage once at app start
await DraftManager.initStorage();

// Create draft manager
final draftManager = DraftManager(
  controller: controller,
  key: 'registration_form',
  debounce: Duration(seconds: 2),
);

// Start auto-save
draftManager.start();

// Check for existing draft
if (await draftManager.hasDraft()) {
  final restored = await draftManager.restoreDraft();
  if (restored != null) {
    showSnackBar('Draft restored');
  }
}

// Manual save
await draftManager.saveDraft();

// Clear after successful submission
await controller.submit((values) async {
  await api.save(values);
  await draftManager.clearDraft();
});

// Stop auto-save (keeps draft)
draftManager.stop();

// Cleanup
draftManager.dispose();

DraftManager Properties:

Property Type Default Description
controller FiftyFormController required Form controller to manage
key String required Unique storage key for draft
debounce Duration 2 seconds Delay before auto-save
containerName String? 'fifty_forms_drafts' GetStorage container name

FiftySubmitButton #

FiftySubmitButton(
  controller: controller,
  label: 'SUBMIT',
  icon: Icons.send,
  loadingText: 'SAVING...',
  onPressed: () => controller.submit((values) async {
    await api.save(values);
  }),
  disableWhenInvalid: true,
  expanded: true,
  variant: FiftyButtonVariant.primary,
)

Platform Support #

Platform Support Notes
Android Yes
iOS Yes
macOS Yes
Linux Yes
Windows Yes
Web Yes

Fifty Design Language Integration #

This package is part of Fifty Flutter Kit:

  • fifty_tokens - All spacing, radius, and typography values sourced from design tokens
  • fifty_ui - Form field widgets wrap FDL components (FiftyTextField, FiftyDropdown, FiftyCheckbox, etc.)
  • fifty_theme - Consumes theming from the FDL theme system; no custom theme classes defined
  • fifty_storage - Draft persistence layer integrates with the FDL storage abstraction

Version #

Current: 0.1.0


License #

MIT License - see LICENSE for details.

Part of Fifty Flutter Kit.

0
likes
150
points
175
downloads
screenshot

Publisher

verified publisherfifty.dev

Weekly Downloads

Production-ready form building with validation, multi-step wizards, and draft persistence for the Fifty Flutter Kit.

Homepage
Repository (GitHub)
View/report issues

Topics

#flutter #form #validation #wizard

Documentation

API reference

License

MIT (license)

Dependencies

fifty_storage, fifty_theme, fifty_tokens, fifty_ui, flutter, get_storage

More

Packages that depend on fifty_forms