App Forms
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
- ๐ GitHub Issues
- ๐ฆ Pub.dev Package
- ๐ง Email Support