Smart Form Field (smart_multi_form_fields)
A production-grade, type-safe Flutter form field package that provides a single public widget (SmartFormField) capable of rendering multiple input types through a sealed configuration hierarchy.
Implementation Status
| Type | Config | Status |
|---|---|---|
| Text | SmartTextConfig |
✅ Complete |
| Password | SmartPasswordConfig |
✅ Complete |
| Phone | SmartPhoneConfig |
✅ Complete |
| OTP | SmartOtpConfig |
⏳ Stub |
| Date | SmartDateConfig |
⏳ Stub |
| Dropdown | SmartDropdownConfig |
⏳ Stub |
| File | SmartFileConfig |
⏳ Stub |
Features
- 📱 Supported Platforms: Tested and officially supported on Android & iOS.
- 🎯 Single Public Widget:
SmartFormField(config: ...)for every input type. - 🔒 Type-Safe Sealed Configs:
SmartFieldConfigis sealed — compile-time safety, zero dead properties. - 🛡️ Built-in Validation: Required check,
minLength,maxLength, custom validator chain (SmartValidators.email). - 🔑 Password Strength & Custom Scoring: Animated 4-segment strength meter, custom scoring algorithms, custom meter UI, obscuring character selection (
•,*), custom eye icons. - 🔄 Confirm Password Matching: Real-time exact string match validation via
confirmPasswordController. - ✍️ Formatting & Capitalization:
autoCapitalizeWords, custominputFormatters, auto-trimming. - 🔌 External Controller: Optional
controllerproperty — you own disposal if provided, package handles it if omitted. - 🎨 Automatic Theming: Seamlessly inherits app
InputDecorationThemeand light/darkColorScheme. - ☎️ Phone Field with Real Validation: Country-code picker with flags,
libphonenumber-derived per-country format validation, and E.164-formatted output — no manual dial-code string manipulation needed. - ⏱ Debounced & Async Search:
SmartTextConfig.search()with race-condition-safe async execution. - 🔑 Programmatic Control: Validate, reset, read values via
GlobalKey<SmartBaseShellState>.
Installation
dependencies:
smart_multi_form_fields: ^1.1.0
Usage Examples & Previews
import 'package:smart_multi_form_fields/smart_multi_form_fields.dart';
Section 1: Text Input (SmartTextConfig)
SmartTextField Previews
| Basic Text & Email Validation | Bio Multiline & Character Counter |
|---|---|
1. Basic Text Input & Validation
SmartFormField(
config: SmartTextConfig(
label: 'Full Name',
hint: 'John Doe',
isRequired: true,
autoCapitalizeWords: true,
prefixIcon: const Icon(Icons.person),
validators: [
(value) => value!.length < 3 ? 'Name must be at least 3 characters' : null,
],
),
)
2. Email Field with Built-in Validator
SmartFormField(
config: SmartTextConfig(
label: 'Email Address',
hint: 'user@example.com',
isRequired: true,
validators: [SmartValidators.email],
prefixIcon: const Icon(Icons.email),
),
)
3. Multiline & Character Counter (Bio / Notes)
SmartFormField(
config: SmartTextConfig(
label: 'Bio',
hint: 'Write a short bio...',
minLength: 10,
maxLength: 200,
maxLines: 4,
minLines: 2,
prefixIcon: const Icon(Icons.info),
),
)
4. Auto-Clear Button & Icons
SmartFormField(
config: SmartTextConfig(
label: 'Address',
hint: 'Enter your address',
showClearButton: true, // Auto-shows (×) clear icon when non-empty
prefixIcon: const Icon(Icons.location_on),
),
)
5. Async Search Field with Debounce
SmartFormField(
config: SmartTextConfig.search(
label: 'Search Products',
hint: 'Type product name...',
debounce: const Duration(milliseconds: 400),
onSearchAsync: (query, isCurrent) async {
final results = await myApi.searchProducts(query);
if (isCurrent()) { // Ensures older, slower API responses don't overwrite newer results
setState(() => _searchResults = results);
}
},
),
)
6. External Controller & Focus Traversal
final _nameController = TextEditingController();
final _nextFocus = FocusNode();
SmartFormField(
config: SmartTextConfig(
label: 'First Name',
controller: _nameController,
nextFocusNode: _nextFocus,
textInputAction: TextInputAction.next,
),
)
// Read or pre-fill value anytime:
_nameController.text = 'Pre-filled';
7. Programmatic Validation via GlobalKey
final _formKey = GlobalKey<SmartBaseShellState>();
SmartFormField(
key: _formKey,
config: SmartTextConfig(label: 'Username', isRequired: true),
)
// On submit button click:
void onSubmit() {
final error = _formKey.currentState?.validate();
if (error == null) {
final value = _formKey.currentState?.value;
print('Valid value: $value');
}
}
Section 2: Password Input (SmartPasswordConfig)
SmartPasswordField Previews
| Strength Meter & Custom Policy | Custom Validators & Live Values Card |
|---|---|
1. Password with Strength Meter
SmartFormField(
config: SmartPasswordConfig(
label: 'Password',
hint: 'Enter your password',
isRequired: true,
minPasswordLength: 8,
showStrengthMeter: true, // Animated 4-segment strength bar
),
)
2. Strict Password with Complexity Rules
SmartFormField(
config: SmartPasswordConfig(
label: 'Strong Password',
isRequired: true,
minPasswordLength: 8,
requireUppercase: true, // Must include A-Z
requireLowercase: true, // Must include a-z
requireDigit: true, // Must include 0-9
requireSpecialChar: true, // Must include !@#$%^&*
),
)
3. Confirm Password Match Validation
final _passwordController = TextEditingController();
// Original password field
SmartFormField(
config: SmartPasswordConfig(
label: 'Password',
controller: _passwordController,
isRequired: true,
),
)
// Confirm password field — compares value against original field
SmartFormField(
config: SmartPasswordConfig(
label: 'Confirm Password',
confirmPasswordController: _passwordController,
confirmMismatchMessage: 'Passwords do not match',
showStrengthMeter: false,
isRequired: true,
),
)
4. Custom Obscuring Character & Custom Toggle Icon
SmartFormField(
config: SmartPasswordConfig(
label: 'Custom Password',
obscuringCharacter: '*', // Mask symbol (* instead of default •)
toggleIconBuilder: (isObscured) => Icon(
isObscured ? Icons.lock_outline : Icons.lock_open_outlined,
color: Colors.blue,
size: 20,
),
),
)
5. Custom Strength Scorer & Custom Strength UI Builder
SmartFormField(
config: SmartPasswordConfig(
label: 'Custom Policy Password',
// Custom scoring algorithm
customStrengthScorer: (password, minLength) {
if (password.length < minLength) return PasswordStrength.weak;
if (password.contains('123456')) return PasswordStrength.weak;
return PasswordStrengthScorer.score(password, minLength: minLength);
},
// Custom strength meter UI replacement
strengthMeterBuilder: (context, strength) {
return Text('Strength: ${strength.label}', style: TextStyle(color: strength.color));
},
),
)
Section 3: Phone Input (SmartPhoneConfig)
SmartPhoneField Previews
| Phone Field Implementation | Validation & Selection |
|---|---|
1. Basic Phone Field
SmartFormField(
config: SmartPhoneConfig(
label: 'Phone Number',
defaultCountryCode: 'IN', // required — pick your app's primary market
isRequired: true,
helperText: 'We\'ll text you a verification code',
),
)
2. Custom Error Message & Country Change Callback
SmartFormField(
config: SmartPhoneConfig(
label: 'Phone Number',
defaultCountryCode: 'GB',
invalidNumberMessage: 'That doesn\'t look like a valid number',
onCountryChanged: (isoCode, dialCode) {
print('User switched to $isoCode ($dialCode)');
},
),
)
3. Getting the Full E.164 Value
final _phoneKey = GlobalKey<SmartBaseShellState>();
SmartFormField(
key: _phoneKey,
config: SmartPhoneConfig(label: 'Phone', defaultCountryCode: 'US'),
)
// Always returns the full international format, e.g. "+14155551234":
final phone = _phoneKey.currentState?.value;
Important: defaultCountryCode is required by design — this package does not attempt to auto-detect a user's country from device locale, since a phone's display-language setting is not a reliable proxy for actual location. Set it to whichever country your app primarily targets; users elsewhere can switch via the flag picker at any time.
SmartTextConfig Property Reference
| Property | Type | Description |
|---|---|---|
label |
String? |
Field label header shown above input |
hint |
String? |
Placeholder text inside input |
helperText |
String? |
Supporting text shown below field |
isRequired |
bool |
Adds * asterisk and runs empty validation |
validators |
List<SmartValidator> |
Custom validation functions chain |
minLength |
int? |
Minimum character length constraint |
maxLength |
int? |
Maximum character length constraint (renders counter) |
maxLines |
int? |
Maximum lines for multiline input |
minLines |
int? |
Minimum lines for multiline input |
autoCapitalizeWords |
bool |
Automatically capitalizes first letter of every word |
showClearButton |
bool |
Renders (×) clear button when text is non-empty |
prefixIcon |
Widget? |
Icon displayed at the start of input |
suffixIcon |
Widget? |
Icon displayed at the end of input |
onSuffixIconTap |
VoidCallback? |
Tap handler for suffixIcon |
readOnly |
bool |
Focusable and selectable but prevents editing |
enabled |
bool |
Disables field interactions and dims colors |
controller |
TextEditingController? |
External controller override |
focusNode |
FocusNode? |
External focus node override |
nextFocusNode |
FocusNode? |
Focus node to request on keyboard submit |
debounce |
Duration |
Delay for debounced callbacks (default: 300ms) |
onDebouncedChanged |
ValueChanged<String>? |
Sync callback invoked after debounce delay |
onSearchAsync |
Future<void> Function(query, isCurrent)? |
Async search callback with stale response checker |
showSearchLoadingIndicator |
bool |
Auto-swaps suffix icon to spinner during async search |
SmartPasswordConfig Property Reference
| Property | Type | Description |
|---|---|---|
label |
String? |
Field label header shown above input |
hint |
String? |
Placeholder text inside input |
helperText |
String? |
Supporting text shown below field |
isRequired |
bool |
Adds * asterisk and runs empty validation |
obscuringCharacter |
String |
Mask symbol used when obscured (default: '•') |
showToggleIcon |
bool |
Renders visibility eye icon toggle (default: true) |
toggleIconBuilder |
Widget Function(bool isObscured)? |
Builder for custom visibility toggle icon |
showStrengthMeter |
bool |
Renders animated 4-segment password strength bar (default: true) |
strengthMeterBuilder |
Widget Function(BuildContext, PasswordStrength)? |
Builder for replacing default strength meter UI |
customStrengthScorer |
PasswordStrength Function(String, int)? |
Custom algorithm function for password strength scoring |
minPasswordLength |
int |
Minimum password length requirement & strength threshold baseline (default: 8) |
requireUppercase |
bool |
Requires at least one uppercase letter [A-Z] |
requireLowercase |
bool |
Requires at least one lowercase letter [a-z] |
requireDigit |
bool |
Requires at least one numeric digit [0-9] |
requireSpecialChar |
bool |
Requires at least one special character [!@#$%^&*...] |
confirmPasswordController |
TextEditingController? |
Controller of original password field for match validation |
confirmMismatchMessage |
String? |
Custom error message when confirm password does not match |
SmartPhoneConfig Property Reference
| Property | Type | Description |
|---|---|---|
label |
String? |
Field label header shown above input |
hint |
String? |
Placeholder text inside input |
helperText |
String? |
Supporting text shown below field |
isRequired |
bool |
Adds * asterisk and runs empty validation |
defaultCountryCode |
String |
Required. ISO 3166-1 alpha-2 starting country (e.g. 'IN', 'US', 'GB') |
onCountryChanged |
void Function(String isoCode, String dialCode)? |
Fires when the user picks a different country |
invalidNumberMessage |
String? |
Custom error message for format validation failure |
showDropdownIcon |
bool |
Show/hide the chevron next to the flag (flag stays tappable either way) |
flagsButtonPadding |
EdgeInsetsGeometry? |
Padding around the flag/dial-code button |
flagsButtonMargin |
EdgeInsets? |
Margin around the flag/dial-code button |
validateMode |
AutovalidateMode |
Controls live re-validation behavior as the user types |
controller |
TextEditingController? |
External controller override (holds the national number) |
focusNode / nextFocusNode |
FocusNode? |
External focus control / focus traversal target |
validators |
List<SmartValidator> |
Custom validators — receive the raw national number, no dial code |
🤝 Community, Feedback & Contributing
Why use smart_multi_form_fields?
Form building in Flutter shouldn't require copy-pasting hundreds of lines of TextFormField boilerplate or mixing separate third-party packages for every input type.
smart_multi_form_fields unifies all your form fields into a single, production-grade widget with compile-time type safety, theme adaptability, built-in validation rules, animated strength feedback, and zero maintenance bloat.
If this package saves you development time or makes your Flutter codebase cleaner, please ⭐ star the repository on GitHub to support the project!
🐛 How to Report Issues & Request Features
We welcome feature requests, bug reports, and pull requests! To ensure issues are resolved as quickly as possible, please follow these guidelines when opening a GitHub issue.
Steps to File an Issue:
- Check existing GitHub Issues to verify your bug or feature request hasn't already been reported.
- Click New Issue on the repository's Issues tab.
- Provide the required details listed below.
Required Issue Details Checklist:
- 📌 Environment Info: Include output of
flutter doctor -v(Flutter SDK and Dart versions). - 📦 Package Version: State the package version used (e.g.
smart_multi_form_fields: ^1.1.0). - 🧩 Config Type: Specify which configuration was used (e.g.
SmartTextConfigorSmartPasswordConfig). - 📝 Minimal Reproducible Code: Provide a complete, self-contained Flutter snippet reproducing the behavior.
- 🎯 Expected vs Actual Behavior: Clearly describe what should happen vs what actually occurred.
- 📷 Screenshots / Logs: Attach error stack traces or visual screenshots if applicable.
License
MIT License.