App Forms

Pub Version License

A powerful Flutter package that separates form logic from UI components using flutter_form_builder. Build maintainable, testable, and reusable forms with clean architecture principles.

โœจ Features

  • ๐Ÿ—๏ธ Clean Architecture: Separate form logic from UI components
  • ๐Ÿ’‰ Dependency Injection: Global form management with singleton pattern
  • ๐Ÿ”„ Reactive State Management: Real-time form state updates and validation
  • โšก Auto-validation: Configurable automatic field validation with debouncing
  • ๐ŸŽฏ Type Safety: Generic type support for form fields
  • ๐Ÿ“‹ Field Management: Advanced field operations (setValue, reset, validation)
  • ๐Ÿ” Form State Tracking: Built-in loading, progress, error, and success states
  • ๐ŸŽญ Lifecycle Hooks: onInit, onSubmit, onValid, and onReset callbacks
  • ๐Ÿš€ Performance Optimized: Debounced field changes (250ms) for optimal performance
  • ๐Ÿ“ฆ Easy Integration: Works seamlessly with flutter_form_builder

๐Ÿš€ Quick Start

Installation

Add the package to your pubspec.yaml:

dependencies:
  app_forms: ^0.6.0
  flutter_form_builder: ^10.1.0
  form_builder_validators: ^11.2.0

Basic Usage

1. Create Your Form Class

import 'package:app_forms/app_forms.dart';
import 'package:form_builder_validators/form_builder_validators.dart';

class LoginForm extends AppForm {
  // Configure auto-validation
  @override
  bool get autoValidate => true;

  // Define form fields with validation and callbacks
  final email = AppFormField<String>(
    name: 'email',
    initialValue: '[email protected]',
    validations: FormBuilderValidators.compose([
      FormBuilderValidators.required(),
      FormBuilderValidators.email(),
    ]),
    onChange: (field) {
      print('Email changed: ${field?.value}');
    },
    onValid: (field) {
      print('Email is valid: ${field?.value}');
    },
  );

  final password = AppFormField<String>(
    name: 'password',
    validations: FormBuilderValidators.compose([
      FormBuilderValidators.required(),
      FormBuilderValidators.minLength(6),
    ]),
  );

  LoginForm() {
    setFields([email, password]);
  }

  @override
  Future<void> onInit() async {
    // Initialize form data, API calls, etc.
    print('Form initialized');
  }

  @override
  Future<void> onSubmit(Map<String, dynamic>? values) async {
    // Handle form submission
    print('Submitting: $values');
    
    try {
      // Simulate API call
      await Future.delayed(const Duration(seconds: 2));
      setSuccess(true);
    } catch (e) {
      setHasErrors(true);
    }
  }

  @override
  Future<void> onValid(Map<String, dynamic>? values) async {
    // Called when form becomes valid
    print('Form is valid: $values');
  }

  @override
  void onReset() {
    // Handle form reset
    print('Form reset');
  }
}

2. Inject Forms in main.dart

import 'package:app_forms/app_forms.dart';

void main() {
  // Inject your forms globally
  AppForms.injectForms([
    LoginForm(),
    // Add other forms here
  ]);
  
  runApp(MyApp());
}

3. Build Your UI

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Login')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            // Form Builder - handles form state and validation
            AppFormBuilder<LoginForm>(
              builder: (form) {
                return Column(
                  children: [
                    FormBuilderTextField(
                      name: form.email.name,
                      validator: form.email.validations,
                      decoration: const InputDecoration(
                        labelText: 'Email',
                        border: OutlineInputBorder(),
                      ),
                    ),
                    const SizedBox(height: 16),
                    FormBuilderTextField(
                      name: form.password.name,
                      validator: form.password.validations,
                      obscureText: true,
                      decoration: const InputDecoration(
                        labelText: 'Password',
                        border: OutlineInputBorder(),
                      ),
                    ),
                  ],
                );
              },
            ),
            const SizedBox(height: 24),
            
            // Form Listener - reacts to form state changes
            AppFormListener<LoginForm>(
              builder: (form) {
                return Column(
                  children: [
                    ElevatedButton(
                      onPressed: form.progressing ? null : form.submit,
                      child: form.progressing
                          ? const CircularProgressIndicator()
                          : const Text('Login'),
                    ),
                    if (form.hasErrors)
                      const Text(
                        'Please fix errors above',
                        style: TextStyle(color: Colors.red),
                      ),
                    if (form.success)
                      const Text(
                        'Login successful!',
                        style: TextStyle(color: Colors.green),
                      ),
                  ],
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

๐Ÿ“š Advanced Features

Form State Management

class MyForm extends AppForm {
  void customAction() {
    // Control form states
    setLoading(true);        // Show loading state
    setHasErrors(false);     // Clear errors
    setSuccess(false);       // Clear success state
    
    // Access form values
    final values = getValues();
    
    // Validate programmatically
    final isValid = saveAndValidate();
    
    // Reset form
    reset();
  }
}

Dynamic Field Updates

class MyForm extends AppForm {
  void updateFieldValue() {
    // Update field value programmatically
    email.value = '[email protected]';
    updateFieldsValue(); // Sync with form builder
  }
  
  void setServerErrors(Map<String, dynamic> errors) {
    // Set validation errors from server
    setValidationErrors({
      'email': 'Email already exists',
      'password': 'Password too weak'
    });
  }
}

Custom Validation

final customField = AppFormField<String>(
  name: 'username',
  validations: (value) {
    if (value == null || value.isEmpty) {
      return 'Username is required';
    }
    if (value.length < 3) {
      return 'Username must be at least 3 characters';
    }
    return null; // Valid
  },
);

๐Ÿ›๏ธ Architecture

Core Components

  • AppForm: Abstract base class for all forms
  • AppFormField: Type-safe field configuration
  • AppForms: Singleton service for dependency injection
  • AppFormBuilder: Widget wrapper connecting forms to UI
  • AppFormListener: Reactive widget for form state changes

Flow Diagram

Form Definition โ†’ Dependency Injection โ†’ UI Binding โ†’ State Management
     โ†“                    โ†“                โ†“             โ†“
  AppForm           AppForms.inject    AppFormBuilder  AppFormListener

๐Ÿ”ง Configuration Options

Auto-validation Control

class MyForm extends AppForm {
  @override
  bool get autoValidate => false; // Disable auto-validation
}

Custom Debouncing

The package uses a 250ms debouncer by default. This is configured internally but optimized for most use cases.

๐Ÿงช Testing

void main() {
  group('LoginForm Tests', () {
    late LoginForm form;
    
    setUp(() {
      form = LoginForm();
      AppForms.injectForms([form]);
    });
    
    test('should validate email field', () {
      form.email.setValue('invalid-email');
      expect(form.saveAndValidate(), false);
      
      form.email.setValue('[email protected]');
      expect(form.saveAndValidate(), true);
    });
  });
}

๐Ÿ“– API Reference

See the API documentation for detailed information about all available methods and properties.

๐Ÿค Contributing

Contributions are welcome! Please read our contributing guidelines and submit pull requests to our GitHub repository.

๐Ÿ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

๐Ÿ†˜ Support

Libraries

app_forms