TextFieldAppi
An advanced text input widget with built-in validation, styling, and enhanced user experience features.
Overview
TextFieldAppi
is a comprehensive text input component that extends Flutter's TextFormField
with additional features like advanced validation, custom styling, and better accessibility. It's designed to handle all your text input needs with a consistent API.
Features
- ✅ Built-in Validation - Multiple validation rules
- 🎨 Custom Styling - Extensive theming options
- 🔍 Search Integration - Built-in search capabilities
- ♿ Accessibility - Screen reader and keyboard support
- 📱 Responsive - Adapts to different screen sizes
- 🌙 Theme Support - Light and dark mode ready
Basic Usage
TextFieldAppi(
label: 'Email',
hintText: 'Enter your email address',
onChanged: (value) {
print('Email: $value');
},
)
Properties
Property | Type | Default | Description |
---|---|---|---|
label | String? | null | Field label text |
hintText | String? | null | Placeholder text |
initialValue | String? | null | Initial field value |
controller | TextEditingController? | null | Text controller |
validator | String? Function(String?)? | null | Validation function |
onChanged | ValueChanged<String>? | null | Value change callback |
onSubmitted | ValueChanged<String>? | null | Submit callback |
keyboardType | TextInputType? | null | Keyboard type |
textInputAction | TextInputAction? | null | Input action |
obscureText | bool | false | Hide text (for passwords) |
enabled | bool | true | Enable/disable field |
readOnly | bool | false | Read-only mode |
maxLines | int? | 1 | Maximum lines |
maxLength | int? | null | Maximum character length |
borderRadius | double? | null | Border radius |
borderColor | Color? | null | Border color |
focusedBorderColor | Color? | null | Focused border color |
fillColor | Color? | null | Background fill color |
textStyle | TextStyle? | null | Text styling |
labelStyle | TextStyle? | null | Label styling |
hintStyle | TextStyle? | null | Hint text styling |
Examples
Basic Text Field
TextFieldAppi(
label: 'Full Name',
hintText: 'Enter your full name',
onChanged: (value) {
setState(() {
fullName = value;
});
},
)
Email Field with Validation
TextFieldAppi(
label: 'Email Address',
hintText: 'example@domain.com',
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Email is required';
}
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
return 'Please enter a valid email';
}
return null;
},
onChanged: (value) {
setState(() {
email = value;
});
},
)
Password Field
class PasswordField extends StatefulWidget {
_PasswordFieldState createState() => _PasswordFieldState();
}
class _PasswordFieldState extends State<PasswordField> {
bool _obscureText = true;
Widget build(BuildContext context) {
return TextFieldAppi(
label: 'Password',
hintText: 'Enter your password',
obscureText: _obscureText,
keyboardType: TextInputType.visiblePassword,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Password is required';
}
if (value.length < 8) {
return 'Password must be at least 8 characters';
}
return null;
},
suffixIcon: IconButton(
icon: Icon(_obscureText ? Icons.visibility : Icons.visibility_off),
onPressed: () {
setState(() {
_obscureText = !_obscureText;
});
},
),
);
}
}
Styled Text Field
TextFieldAppi(
label: 'Styled Input',
hintText: 'Custom styled field',
borderRadius: 16,
borderColor: Colors.blue[300],
focusedBorderColor: Colors.blue,
fillColor: Colors.blue[50],
textStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
labelStyle: TextStyle(
color: Colors.blue[700],
fontWeight: FontWeight.w600,
),
)
Multiline Text Field
TextFieldAppi(
label: 'Description',
hintText: 'Enter a detailed description...',
maxLines: 4,
maxLength: 500,
keyboardType: TextInputType.multiline,
textInputAction: TextInputAction.newline,
validator: (value) {
if (value != null && value.length > 500) {
return 'Description must be less than 500 characters';
}
return null;
},
)
Validation Examples
Required Field
TextFieldAppi(
label: 'Required Field',
validator: (value) {
if (value == null || value.isEmpty) {
return 'This field is required';
}
return null;
},
)
Phone Number Validation
TextFieldAppi(
label: 'Phone Number',
hintText: '+1 (555) 123-4567',
keyboardType: TextInputType.phone,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Phone number is required';
}
if (!RegExp(r'^\+?[\d\s\-\(\)]+$').hasMatch(value)) {
return 'Please enter a valid phone number';
}
return null;
},
)
Number Validation
TextFieldAppi(
label: 'Age',
hintText: 'Enter your age',
keyboardType: TextInputType.number,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Age is required';
}
final age = int.tryParse(value);
if (age == null) {
return 'Please enter a valid number';
}
if (age < 18 || age > 120) {
return 'Age must be between 18 and 120';
}
return null;
},
)
Custom Validation
TextFieldAppi(
label: 'Username',
hintText: 'Choose a username',
validator: (value) {
if (value == null || value.isEmpty) {
return 'Username is required';
}
if (value.length < 3) {
return 'Username must be at least 3 characters';
}
if (!RegExp(r'^[a-zA-Z0-9_]+$').hasMatch(value)) {
return 'Username can only contain letters, numbers, and underscores';
}
return null;
},
)
Advanced Features
Search Field
class SearchField extends StatefulWidget {
final Function(String) onSearch;
const SearchField({Key? key, required this.onSearch}) : super(key: key);
_SearchFieldState createState() => _SearchFieldState();
}
class _SearchFieldState extends State<SearchField> {
final _controller = TextEditingController();
Timer? _debounce;
void dispose() {
_debounce?.cancel();
_controller.dispose();
super.dispose();
}
void _onSearchChanged(String query) {
if (_debounce?.isActive ?? false) _debounce!.cancel();
_debounce = Timer(const Duration(milliseconds: 500), () {
widget.onSearch(query);
});
}
Widget build(BuildContext context) {
return TextFieldAppi(
controller: _controller,
label: 'Search',
hintText: 'Search for items...',
prefixIcon: Icon(Icons.search),
suffixIcon: _controller.text.isNotEmpty
? IconButton(
icon: Icon(Icons.clear),
onPressed: () {
_controller.clear();
widget.onSearch('');
},
)
: null,
onChanged: _onSearchChanged,
);
}
}
Form Integration
class UserForm extends StatefulWidget {
_UserFormState createState() => _UserFormState();
}
class _UserFormState extends State<UserForm> {
final _formKey = GlobalKey<FormState>();
final _nameController = TextEditingController();
final _emailController = TextEditingController();
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFieldAppi(
controller: _nameController,
label: 'Full Name',
validator: (value) {
if (value == null || value.isEmpty) {
return 'Name is required';
}
return null;
},
),
SizedBox(height: 16),
TextFieldAppi(
controller: _emailController,
label: 'Email',
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Email is required';
}
if (!value.contains('@')) {
return 'Please enter a valid email';
}
return null;
},
),
SizedBox(height: 24),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// Process form data
print('Name: ${_nameController.text}');
print('Email: ${_emailController.text}');
}
},
child: Text('Submit'),
),
],
),
);
}
}
Theming
Custom Theme
class CustomTextFieldTheme {
static InputDecorationTheme get theme => InputDecorationTheme(
filled: true,
fillColor: Colors.grey[100],
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey[300]!),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey[300]!),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.blue, width: 2),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.red),
),
labelStyle: TextStyle(
color: Colors.grey[700],
fontWeight: FontWeight.w500,
),
hintStyle: TextStyle(
color: Colors.grey[500],
),
);
}
Dark Theme Support
TextFieldAppi(
label: 'Dark Theme Field',
fillColor: Theme.of(context).brightness == Brightness.dark
? Colors.grey[800]
: Colors.grey[100],
borderColor: Theme.of(context).brightness == Brightness.dark
? Colors.grey[600]
: Colors.grey[300],
textStyle: TextStyle(
color: Theme.of(context).textTheme.bodyLarge?.color,
),
)
Best Practices
1. Use Proper Validation
// Good: Comprehensive validation
TextFieldAppi(
validator: (value) {
if (value == null || value.isEmpty) return 'Required';
if (value.length < 3) return 'Too short';
if (!RegExp(r'^[a-zA-Z\s]+$').hasMatch(value)) return 'Invalid format';
return null;
},
)
2. Provide Clear Labels and Hints
// Good: Clear and descriptive
TextFieldAppi(
label: 'Email Address',
hintText: 'example@domain.com',
)
// Avoid: Vague labels
TextFieldAppi(
label: 'Input',
hintText: 'Enter text',
)
3. Use Appropriate Keyboard Types
// Good: Specific keyboard types
TextFieldAppi(
keyboardType: TextInputType.emailAddress, // For email
)
TextFieldAppi(
keyboardType: TextInputType.phone, // For phone numbers
)
4. Handle State Properly
// Good: Proper state management
class MyForm extends StatefulWidget {
_MyFormState createState() => _MyFormState();
}
class _MyFormState extends State<MyForm> {
final _controller = TextEditingController();
void dispose() {
_controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return TextFieldAppi(controller: _controller);
}
}
Common Use Cases
1. Login Form
Column(
children: [
TextFieldAppi(
label: 'Email',
keyboardType: TextInputType.emailAddress,
validator: emailValidator,
),
SizedBox(height: 16),
TextFieldAppi(
label: 'Password',
obscureText: true,
validator: passwordValidator,
),
],
)
2. Profile Form
Column(
children: [
TextFieldAppi(
label: 'First Name',
validator: requiredValidator,
),
SizedBox(height: 16),
TextFieldAppi(
label: 'Last Name',
validator: requiredValidator,
),
SizedBox(height: 16),
TextFieldAppi(
label: 'Bio',
maxLines: 3,
maxLength: 200,
),
],
)
3. Search Interface
TextFieldAppi(
label: 'Search Products',
prefixIcon: Icon(Icons.search),
onChanged: (query) => searchProducts(query),
)
Related Components
- SearchableTextFieldAppi - For search functionality
- NumberKeypadAppi - For numeric input
- QuillEditorAppi - For rich text editing
Ready for advanced text input? Check out SearchableTextFieldAppi for search capabilities!