Creating a Smart Form Autofill Feature in Flutter using
Local Data.
If you’re looking for the best Flutter app development company for your mobile application then feel free to
contact us at — support@flutterdevs.com
Table of Contents:
Introduction
What Is Smart Autofill and Why Does It Matter in Flutter
Choosing the Right Storage Strategy
Setting Up the Project: Dependencies and Configuration
Designing the SQLite Database Schema
Building the Database Service
The Autofill Suggestion Engine
Creating the Smart AutofillTextField Widget
Personal Information Form — Full Implementation
Address Form with Smart Suggestions
Contact Form with Name Splitting
Saved Data Screen — View, Search, and Delete
Settings Screen
Real-World Use Cases
Common Mistakes to Avoid
Conclusion and Next Steps
References
Introduction:
Smart form autofill is one of those features that quietly transforms an ordinary mobile application into a genuinely intelligent product. Instead of forcing users to retype the same name, email address, or phone number every time they fill a form, your app learns from their inputs, stores them locally, and surfaces the right suggestion at the right moment — all without sending a single byte to any server.
Flutter, backed by Google’s powerful ecosystem, gives us everything we need to build this kind of feature from scratch: SQLite for structured local storage, GetX for reactive state management, and a rich widget toolkit for building polished, animated suggestion UIs.
This guide walks you through building a production-ready Smart Form Autofill system in Flutter — complete with a fuzzy-ranked suggestion engine, multi-field autofill, phone and date auto-formatting, name splitting, usage tracking, AES-256 encryption for sensitive fields, and a full data management screen. Every step is demonstrated with clean, real Dart code you can drop directly into your project.
What you will learn:
Setting up SQLite with a fully normalized schema and seed data
Building a fuzzy-scored suggestion engine ranked by frequency and recency
Creating a reusable AutofillTextField widget with debounce, animated highlight, and bold match rendering
Implementing personal, address, and contact form modules with GetX
Handling phone number and date auto-formatting with input formatters
Managing saved data — search, edit, delete, bulk clear
Securing sensitive fields with AES-256 encryption toggle
Platform configuration for Android and iOS
What Is Smart Autofill and Why Does It Matter in Flutter?
Smart autofill is more than a simple dropdown list. It is a system that observes what users enter across all forms, stores those values locally, and presents the most relevant suggestion the next time a similar field is focused — ranked by how often and how recently that value was used.
In a Flutter context, this means your app maintains a local form_history table in SQLite. Every time a user successfully saves a form, all non-empty field values are upserted into that table with a usage_count and last_used timestamp. When the user starts typing in any field next time, the suggestion engine queries that table with a fuzzy filter, scores each candidate using a composite algorithm, and renders a live-updating dropdown beneath the field.
Key Benefits:
Improved User Experience: eliminates repetitive manual entry across sessions
Privacy-First: all data stays on the device with zero cloud dependency
Offline Capability: works fully without internet connection
Reduced Errors: suggested values were previously verified by the user
Intelligent Ranking: most-used and most-recent values surface first
Multi-Field Fill: tap a saved profile to populate an entire form instantly
Choosing the Right Storage Strategy
Flutter developers have several options for local data persistence. This project uses a layered approach to match each data type to the most appropriate storage mechanism.
Storage LayerPackageUsed For
SQLite
sqflite
Structured user profiles, addresses, form history,settings
SharedPreferences
shared_preferences
Reactive app settings (autofill on/off, suggestion limit, ranking toggles)
Hive
hive_flutter
Fast key-value cache for extension points
Flutter Secure Storage
flutter_secure_storage
Encrypted storage for sensitive field values when AES toggle is on
Recommendation:
Use SQLite as your primary store for anything relational or queryable. Reserve SharedPreferences for simple boolean or integer settings only. Do not put form history or user profiles in SharedPreferences — it is not designed for querying.
Setting Up the Project: Dependencies and Configuration:
Step 1: Add Dependencies:
Add the following to your pubspec.yaml:
yaml
dependencies:
flutter:
sdk: flutter
# State Management
get: ^4.6.6
# Database
sqflite: ^2.3.2
path_provider: ^2.1.3
path: ^1.9.0
# Local Storage
shared_preferences: ^2.2.3
hive: ^2.2.3
hive_flutter: ^1.1.0
flutter_secure_storage: ^9.2.2
# Utilities
email_validator: ^2.1.17
mask_text_input_formatter: ^2.9.0
intl: ^0.19.0
Run flutter pub get to install.
Step 2: Android Configuration
Set minSdkVersion to 21 in android/app/build.gradle:
kotlin
android {
defaultConfig {
minSdk = 21
targetSdk = 34
} }
Add permissions to android/app/src/main/AndroidManifest.xml:
xml
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
Step 3: iOS Configuration:
Set the minimum iOS version in
ios/Podfile:
ruby
platform :ios, '12.0'
Designing the SQLite Database Schema:
The database has four tables. Each one is purpose-built: users stores full profiles, addresses stores address entries optionally linked to a user, form_history is the engine powering all autofill suggestions, and settings stores key-value app preferences.
users_table :
addresses_table
form_history table
Design Note:
The UNIQUE constraint on (field_name, field_value) is critical. It enables the upsert pattern: INSERT OR IGNORE + UPDATE usage_count. This keeps the table compact and rankings accurate over time.
Building the Database Service:
The DatabaseService is a GetxService registered as a singleton in main.dart. It owns the SQLite connection, creates all tables on first launch, seeds sample data, and exposes typed methods for every CRUD operation the app needs.
Initializing in main.dart:
dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Hive.initFlutter();
await Get.putAsync(() => DatabaseService().init());
runApp(const SmartAutofillApp());
}
Table Creation:
On first launch, onCreate fires and creates all four tables plus indexes. Indexes are non-negotiable — without them, suggestion queries do a full table scan that slows noticeably as history grows:
dart
Future<void> _createTables(Database db, int version) async {
await db.execute('''
CREATE TABLE form_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
field_name TEXT NOT NULL,
field_value TEXT NOT NULL,
field_type TEXT DEFAULT 'text',
usage_count INTEGER DEFAULT 1,
last_used TEXT NOT NULL,
UNIQUE(field_name, field_value) ) ''');
await db.execute( 'CREATE INDEX idx_history_field ON form_history(field_name)' );
await db.execute(
'CREATE INDEX idx_history_usage ON form_history(usage_count DESC)' );
}
The Upsert Pattern for Form History:
Every time a user saves a form, all non-empty field values are upserted into form_history. If the value already exists for that field, usage_count is incremented and last_used is updated. If it is new, a fresh row is inserted:
dart
Future<void> insertOrUpdateFormHistory(FormHistoryModel h) async {
final existing = await db.query(
tableFormHistory,
where: 'field_name = ? AND field_value = ?',
whereArgs: [h.fieldName, h.fieldValue], );
if (existing.isNotEmpty) {
await db.rawUpdate(
'UPDATE form_history SET usage_count = usage_count + 1, '
'last_used = ? WHERE field_name = ? AND field_value = ?',
[DateTime.now().toIso8601String(), h.fieldName, h.fieldValue], );
} else {
await db.insert(tableFormHistory, h.toMap(),
conflictAlgorithm: ConflictAlgorithm.ignore); } }
Ranked Suggestion Query:
Suggestions are fetched with a single optimized query that filters by field name and partial value match, then orders by usage_count DESC and last_used DESC for combined frequency and recency ranking:
dart
Future<List<FormHistoryModel>> getFieldSuggestions(
String fieldName,
String query, {
int limit = 5,
}) async {
final q = '%${query.toLowerCase()}%';
final maps = await db.query(
tableFormHistory,
where: 'field_name = ? AND LOWER(field_value) LIKE ?',
whereArgs: [fieldName, q],
orderBy: 'usage_count DESC, last_used DESC',
limit: limit, );
return maps.map(FormHistoryModel.fromMap).toList(); }
The Autofill Suggestion Engine
The AutofillService is the brain of the system. It queries form_history for candidate values, scores each one using a composite fuzzy algorithm, and returns a ranked list of SuggestionItem objects.
The Scoring Algorithm
Each candidate value receives a score composed of four components:
Exact match: +100 points — the query is identical to the value
Prefix match: +80 points — the value starts with the query
Substring match: +50 points — the value contains the query anywhere
Fuzzy char bonus: 0–20 points — based on what fraction of query characters appear in order in the value
Frequency bonus: log2(clamp(usageCount, 1, 100)) — a log-scaled boost so highly used values rank higher without dominating completely
dart
double _computeMatchScore(String value, String query, int usageCount) {
if (query.isEmpty) return usageCount.toDouble();
final v = value.toLowerCase();
final q = query.toLowerCase();
double score = 0;
if (v == q) {
score += 100; // exact match
} else if (v.startsWith(q)) {
score += 80; // prefix match
} else if (v.contains(q)) {
score += 50; // substring match
}
// Fuzzy character match bonus
int matched = 0;
int qi = 0;
for (int i = 0; i < v.length && qi < q.length; i++) {
if (v[i] == q[qi]) { matched++; qi++; }
}
score += (matched / q.length) * 20;
// Frequency bonus (log scale)
if (usageCount > 0) {
final clamped = usageCount.clamp(1, 100).toDouble();
score += _log2(clamped);
}
return score;
}
double _log2(double x) {
if (x <= 1) return 0;
double result = 0;
double val = x;
while (val > 1) { val /= 2; result += 1; }
return result;
}
Why iterative _log2? Dart's num.clamp() returns num, not double. Passing num to a double parameter causes a type error at runtime. The iterative approach avoids both the extension method anti-pattern and the num-to-double coercion issue entirely.
Recording Usage:
When a user saves a form, all field values are recorded in a single batch call:
dart
Future<void> recordFormSave(Map<String, String> fieldValues) async
{
for (final entry in fieldValues.entries) {
if (entry.value.trim().isNotEmpty) {
await recordUsage(entry.key, entry.value);
}
}
}
Smart Field Helpers:
The service also exposes static utility methods used across all form modules:
dart
// Auto-format phone number: +91 98765 43210
static String formatPhone(String raw) {
final digits = raw.replaceAll(RegExp(r'\D'), '');
String local = digits;
if (local.startsWith('91') && local.length > 10) {
local = local.substring(2);
}
if (local.length > 5) {
return '+91 ${local.substring(0, 5)} ${local.substring(5, local.length.clamp(0, 10))}';
}
if (local.isNotEmpty) return '+91 $local';
return '';
}
// Split full name into first + last
static ({String firstName, String lastName}) splitFullName(String fullName)
{
final parts = fullName.trim().split(RegExp(r'\s+'));
if (parts.isEmpty) return (firstName: '', lastName: '');
if (parts.length == 1) return (firstName: parts[0], lastName: '');
return (firstName: parts.first, lastName: parts.sublist(1).join(' '));
}
Creating the Smart AutofillTextField Widget:
The AutofillTextField widget is the core UI primitive of the entire app. Drop it into any form in place of a standard TextFormField and it gains real-time suggestion querying, debouncing, animated autofill highlight, bold query-match rendering, focus chaining, and a one-tap clear button — all transparently.
Widget Architecture:
StatefulWidget with SingleTickerProviderStateMixin for the highlight animation
Debounced DB queries — 120ms default, configurable per field
On-focus shows recent suggestions (empty query path)
On-blur hides dropdown after 300ms delay to allow tap-to-select
AnimationController drives a color tween: green fill fades to transparent over 600ms on autofill
Bold highlight renders the matched query characters in blue within each suggestion row
Debounced Query:
dart
void _loadSuggestions(String query) {
_debounce?.cancel();
_debounce = Timer(Duration(milliseconds: widget.debounceMs), () async {
if (!mounted) return;
final fieldKey = _labelToFieldKey(widget.label);
final sugs = await AutofillService.to.getSuggestions(
fieldKey, query, limit: widget.suggestionLimit,
);
if (mounted) {
setState(() {
_suggestions = sugs;
_showSuggestions = sugs.isNotEmpty && _focusNode.hasFocus;
});
}
});
}
Animated Autofill Highlight:
dart
void _selectSuggestion(String value) {
widget.controller.text = value;
setState(() { _showSuggestions = false; _autofilled = true; });
_highlightCtrl.forward(from: 0); // green → transparent over 600ms
Future.delayed(const Duration(milliseconds: 800), () {
if (mounted) setState(() => _autofilled = false);
});
widget.onSuggestionSelected?.call(value);
widget.nextFocusNode?.requestFocus(); // chain to next field
}
Bold Query Match Rendering:
dart
class _HighlightedText extends StatelessWidget {
final String text;
final String query;
@override
Widget build(BuildContext context) {
if (query.isEmpty) return Text(text);
final lower = text.toLowerCase();
final q = query.toLowerCase();
final idx = lower.indexOf(q);
if (idx < 0) return Text(text);
return Text.rich(TextSpan(children: [
if (idx > 0)
TextSpan(text: text.substring(0, idx)),
TextSpan(
text: text.substring(idx, idx + q.length),
style: const TextStyle(
fontWeight: FontWeight.w700,
color: Color(0xFF1565C0), ), ),
TextSpan(text: text.substring(idx + q.length)),
])); } }
Personal Information Form — Full Implementation:
The personal form module follows GetX’s clean MVC separation: a controller owns all business logic and state, a binding lazy-puts the controller, and the view is a pure UI consumer.
PersonalFormController:
The controller holds seven TextEditingControllers, seven FocusNodes, a formKey, and two reactive state variables (isSaving and autofillEnabled). On save it validates the form, writes a UserModel to SQLite, records all field values in form_history, and navigates back:
dart
Future<void> saveForm() async {
if (!formKey.currentState!.validate()) return;
try {
isSaving.value = true;
final user = UserModel(
firstName: firstNameController.text.trim(),
lastName: lastNameController.text.trim(),
email: emailController.text.trim().toLowerCase(),
phone: phoneController.text.trim(),
occupation: occupationController.text.trim(),
organization: orgController.text.trim(),
createdAt: DateTime.now(),
updatedAt: DateTime.now(), );
await _db.insertUser(user);
await _autofill.recordFormSave({
'first_name': user.firstName,
'last_name': user.lastName,
'email': user.email,
'phone': user.phone,
'occupation': user.occupation ?? '', });
Get.back();
} finally {
isSaving.value = false;
} }
Chained Focus Navigation:
Focus nodes are wired so pressing Done on one field automatically moves focus to the next, giving the form a natural keyboard navigation flow:
dart
void onFieldSuggestionSelected(String fieldName, String value) {
switch (fieldName) {
case 'first_name':
firstNameController.text = value;
lastNameFocus.requestFocus();
break;
case 'last_name':
lastNameController.text = value;
emailFocus.requestFocus();
break;
case 'email':
emailController.text = value;
phoneFocus.requestFocus();
break; } }
Date Input Auto-Formatter:
A custom TextInputFormatter inserts slashes automatically as the user types, producing DD/MM/YYYY format without any manual cursor handling:
dart
class _DateInputFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue old, TextEditingValue next) {
final digits = next.text.replaceAll('/', '');
final buf = StringBuffer();
for (int i = 0; i < digits.length; i++) {
buf.write(digits[i]);
if ((i == 1 || i == 3) && i != digits.length – 1) {
buf.write('/'); } }
final fmt = buf.toString();
return next.copyWith(
text: fmt,
selection: TextSelection.collapsed(offset: fmt.length),
); } }
Address Form with Smart Suggestions:
The address form demonstrates city, state, and country fields all wired to AutofillTextField, plus a ChoiceChip row for selecting address type (home, work, shipping, billing). The AddressFormController listens to each controller's addListener callback and queries form_history for matching values in real time.
Address Type Selection:
dart
Obx(() => Wrap(
spacing: 8,
children: controller.addressTypes.map((type) {
final selected = controller.selectedAddressType.value == type;
return ChoiceChip(
label: Text(type[0].toUpperCase() + type.substring(1)),
selected: selected,
onSelected: (_) => controller.selectAddressType(type),
selectedColor: const Color(0xFF1565C0),
labelStyle: TextStyle(
color: selected ? Colors.white : Colors.grey.shade700,
fontWeight: FontWeight.w600, ), );
}).toList(), )),
Saving with History Recording:
dart
Future<void> saveForm() async {
if (!formKey.currentState!.validate()) return;
final address = AddressModel(
addressLine1: addressLine1Controller.text.trim(),
city: cityController.text.trim(),
state: stateController.text.trim(),
postalCode: postalCodeController.text.trim(),
country: countryController.text.trim(),
addressType: selectedAddressType.value,
createdAt: DateTime.now(),
updatedAt: DateTime.now(), );
await _db.insertAddress(address);
await _autofill.recordFormSave({
'city': address.city,
'state': address.state,
'country': address.country,
}); }
Contact Form with Name Splitting
The contact form introduces an intelligent full-name field. As the user types a name containing a space, the controller detects it and displays a live hint showing how the name will be split into first and last components — exactly as users expect from smart address books.
dart
static ({String firstName, String lastName}) splitFullName(String fullName) {
final parts = fullName.trim().split(RegExp(r'\s+'));
if (parts.isEmpty) return (firstName: '', lastName: '');
if (parts.length == 1) return (firstName: parts[0], lastName: '');
return (
firstName: parts.first,
lastName: parts.sublist(1).join(' '),
);
}
The hint updates reactively via an RxString:
dart
void _onNameChanged() {
final name = fullNameController.text.trim();
if (name.contains(' ')) {
final split = AutofillService.splitFullName(name);
nameSplitHint.value =
'First: "${split.firstName}" Last: "${split.lastName}"';
} else {
nameSplitHint.value = '';
}
}
Rendered in the view:
dart
Obx(() => controller.nameSplitHint.value.isNotEmpty
? Padding(
padding: const EdgeInsets.only(top: 6, left: 4),
child: Row(
children: [
const Icon(Icons.auto_awesome, size: 12, color: Color(0xFF1565C0)),
const SizedBox(width: 4),
Text(
controller.nameSplitHint.value,
style: const TextStyle(
fontSize: 11,
color: Color(0xFF1565C0),
fontWeight: FontWeight.w500,
),
),
],
),
)
: const SizedBox.shrink()),
Saved Data Screen — View, Search, and Delete:
The saved data screen uses a DefaultTabController with three tabs: Profiles, Addresses, and Statistics. A shared search bar at the top filters across both profiles and addresses reactively via GetX computed getters.
Reactive Search Filter:
dart
List<UserModel> get filteredUsers {
final q = searchQuery.value.toLowerCase();
if (q.isEmpty) return users;
return users.where((u) =>
u.fullName.toLowerCase().contains(q) ||
u.email.toLowerCase().contains(q) ||
u.phone.contains(q)
).toList();
}
Statistics Tab
The Statistics tab renders a 2×2 metric card grid and a LinearProgressIndicator bar chart showing field usage frequency — all fed from live SQLite aggregation:
dart
Future<Map<String, int>> getFieldUsageStats() async {
final maps = await db.rawQuery('''
SELECT field_name, SUM(usage_count) as total
FROM form_history
GROUP BY field_name
ORDER BY total DESC
''');
return { for (var m in maps)
m['field_name'] as String : m['total'] as int };
}
Delete with Confirmation Dialog:
dart
void _confirmDelete(BuildContext context, VoidCallback onConfirm) {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: const Text('Confirm Delete'),
content: const Text('This entry will be permanently removed.'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
),
TextButton(
onPressed: () { Navigator.pop(context); onConfirm(); },
style: TextButton.styleFrom(foregroundColor: Colors.red),
child: const Text('Delete'),
),
],
),
);
}
Settings Screen:
The settings screen exposes five reactive toggles and a suggestion-limit slider, all backed by SharedPreferences via SettingsService. Changes propagate instantly across the app because every setting is an Rx variable.
Enable autofill: master on/off switch for all suggestions
Auto-save form data: saves field values to history on every form save
Rank by frequency: heavier weight to high usage_count values
Rank by recency: heavier weight to recent last_used timestamps
Encrypt sensitive fields: toggles AES-256 for email and phone values
dart
Future<void> setAutofillEnabled(bool v) async {
autofillEnabled.value = v;
await _prefs.setBool('autofill_enabled', v);
}
The slider for suggestion limit:
dart
Slider(
value: s.suggestionLimit.value.toDouble(),
min: 3,
max: 10,
divisions: 7,
activeColor: const Color(0xFF1565C0),
onChanged: (v) => s.setSuggestionLimit(v.round()),
),
Real-World Use Cases:
OCR in Flutter finds practical application in many product domains. Smart Form Autofill is equally versatile.
1. E-Commerce Checkout Returning shoppers autofill their shipping address and contact details in one tap. The address type chip (home / work / shipping / billing) makes it easy to switch between saved addresses for different delivery scenarios.
2. CRM and Lead Collection Field sales teams filling contact forms repeatedly benefit from frequency-ranked suggestions — the names and emails they use most often surface first without any typing.
3. Registration Flows Multi-step registration forms can use multi-field autofill to populate the entire first step from a single profile tap, dramatically reducing drop-off rates.
4. Healthcare Intake Forms Patient intake forms with recurring demographic fields benefit from local-only, encrypted autofill — sensitive data never leaves the device.
5. Logistics and Delivery Apps Warehouse and delivery workers entering recipient addresses repeatedly see the most frequently entered addresses ranked first, reducing per-entry time from 30+ seconds to under 3.
6. Banking and KYC Autofilling name, address, and contact fields from a saved profile speeds up Know-Your-Customer onboarding flows while keeping all data on-device and encrypted.
7. Educational Apps Student registration and course enrollment forms benefit from pre-populated personal details, reducing friction for returning users signing up for new courses.
8. Government and Civic Apps Permit applications, service requests, and form submissions that ask for the same personal and address data repeatedly are perfect candidates for frequency-ranked autofill.
Common Mistakes to Avoid
1. Using SharedPreferences for Queryable Data:
SharedPreferences has no query capability. Storing form history as a JSON blob in SharedPreferences makes fuzzy searching and ranking impossible. Use SQLite for any data you need to filter or sort.
dart
// WRONG: storing list in SharedPreferences
await prefs.setString('emails', jsonEncode(emailList));
// CORRECT: upsert into form_history with usage_count
await db.insertOrUpdateFormHistory(history);
2. Not Disposing TextEditingControllers and FocusNodes:
Every controller and focus node allocated in a GetX controller must be disposed in onClose(). Missing FocusNode disposal causes subtle memory leaks that only surface under heavy navigation.
dart
@override
void onClose() {
firstNameController.dispose();
firstNameFocus.dispose(); // do not forget FocusNodes
// … all others
super.onClose();
}
3. Querying on Every Keystroke Without Debounce:
Firing a SQLite query synchronously on every character entered causes jank and unnecessary battery usage. The AutofillTextField widget debounces by 120ms by default — never lower this below 80ms on mid-range devices.
dart
// WRONG: query fires on every character
onChanged: (val) => loadSuggestions(val),
// CORRECT: debounced by 120ms
_debounce = Timer(const Duration(milliseconds: 120), () {
loadSuggestions(val);
});
4. Using .clamp() Result Directly as double:
A common Dart pitfall: int.clamp() returns num, not double. Passing it to a method expecting double produces a type error at runtime. Always call .toDouble() explicitly after .clamp().
dart
// WRONG — clamp returns num, not double
score += _log2(usageCount.toDouble().clamp(1, 100));
// CORRECT — explicit toDouble() after clamp
final clamped = usageCount.clamp(1, 100).toDouble();
score += _log2(clamped);
5. Not Indexing form_history:
Without indexes, suggestion queries do a full table scan. As history grows, queries slow noticeably. Always create indexes on field_name and usage_count during table creation.
dart
await db.execute(
'CREATE INDEX idx_history_field ON form_history(field_name)'
);
await db.execute(
'CREATE INDEX idx_history_usage ON form_history(usage_count DESC)'
);
6. Processing Every Frame Without Throttling (for Camera Features):
If you extend this app with a camera-based OCR autofill feature, never process every camera frame. Always use a processing gate flag to skip frames while one is already being processed, preventing severe performance degradation.
dart
Future<void> _processFrame(CameraImage image) async {
if (_isProcessing) return; // Skip this frame
_isProcessing = true;
try {
// process frame
} finally {
_isProcessing = false;
}
}
“`
7. Hardcoding Field Keys
The `AutofillTextField` widget derives its field key from the label string. If you rename a label after deployment, old suggestions will not surface for that field. Keep label strings stable once shipped, or implement a `form_history` migration that renames affected `field_name` values.
8. Download Complete Source Code
The complete source code for this Flutter Smart Form Autofill application is available on GitHub. This production-ready implementation includes all the features discussed in this tutorial.
Repository Structure:
smart_autofill/
├── lib/
│ ├── main.dart
│ └── app/
│ ├── bindings/
│ │ └── initial_binding.dart
│ ├── data/
│ │ ├── models/
│ │ │ ├── user_model.dart
│ │ │ ├── address_model.dart
│ │ │ └── form_history_model.dart
│ │ └── services/
│ │ ├── database_service.dart
│ │ ├── autofill_service.dart
│ │ └── settings_service.dart
│ ├── modules/
│ │ ├── home/
│ │ ├── personal_form/
│ │ ├── address_form/
│ │ ├── contact_form/
│ │ ├── saved_data/
│ │ └── settings/
│ ├── routes/
│ │ ├── app_routes.dart
│ │ └── app_pages.dart
│ └── widgets/
│ └── autofill_text_field.dart
├── android/
├── ios/
├── pubspec.yaml
└── README.md
Quick Start:
bash
# Clone the repository
git clone https://github.com/onlykrishna/Smart-Form-Autofill.git
# Navigate to project
cd smart-autofill-flutter
# Install dependencies
flutter pub get
# iOS only (macOS)
cd ios && pod install && cd ..
# Run on a physical device for best results
flutter run
Conclusion and Next Steps:
Building smart form autofill in Flutter is fundamentally about three things done well: a properly indexed SQLite schema that grows richer with each use, a scoring algorithm that surfaces the most relevant suggestion without overwhelming the user, and a composable widget that drops into any form without requiring changes to the surrounding code.
The system described in this guide scales from a single-form prototype to a multi-template, encrypted, production app. The AutofillTextField widget is intentionally generic — it queries by label name, so adding a new field to any form automatically gives it suggestion support with zero additional wiring.
Key Takeaways:
Use SQLite for all queryable form data — never SharedPreferences
Debounce suggestion queries at 120ms minimum to prevent jank
Always call .toDouble() explicitly after .clamp() to avoid num type errors in Dart
Index field_name and usage_count in form_history from day one
Dispose every TextEditingController and FocusNode in onClose()
Record field usage on every successful save to build ranking over time
Test on physical devices — emulators mask SQLite and animation performance issues
Extend This Foundation With:
Cloud sync: push form_history to Firestore for cross-device suggestions
Biometric lock: gate autofill behind fingerprint or face authentication
OCR integration: prefill forms by scanning a business card or document
Voice input: populate fields via speech-to-text with autofill fallback
AI field prediction: use on-device ML to predict likely values before typing begins
Export/Import: CSV and JSON round-trip for data portability and backup
References:
sqflite — SQLite plugin for Flutter: sqflite | Flutter package
Flutter plugin for SQLite, a self-contained, high-reliability, embedded, SQL database engine.pub.dev
get — GetX state management and navigation: get | Flutter package
Open screens/snackbars/dialogs without context, manage states and inject dependencies easily with GetX.pub.dev
shared_preferences — Flutter plugin for SharedPreferences:
shared_preferences | Flutter package
Flutter plugin for reading and writing simple key-value pairs. Wraps NSUserDefaults on iOS and SharedPreferences on…pub.dev
flutter_secure_storage — Encrypted key-value storage:
flutter_secure_storage | Flutter package
A Flutter plugin for securely storing sensitive data using encrypted storage.pub.dev
email_validator — Email format validation:
email_validator | Dart package
A simple (but correct) dart class for validating email addressespub.dev
mask_text_input_formatter — Input masking for phone and date:
mask_text_input_formatter | Flutter package
The package provides TextInputFormatter for TextField and TextFormField which format the input by a given mask.pub.dev
Connect With Us:
Feel free to connect with us:And read more articles from FlutterDevs.com.
FlutterDevs team of Flutter developers to build high-quality and functionally-rich apps. Hire a Flutter developer for your cross-platform Flutter mobile app project hourly or full-time as per your requirement! For any flutter-related queries, you can connect with us on Facebook, GitHub, Twitter, and LinkedIn.
We welcome feedback and hope that you share what you’re working on using #FlutterDevs. We truly enjoy seeing how you use Flutter to build beautiful, interactive web experiences.
Need help building production-grade Flutter apps? FlutterDevs helps teams ship faster with solid architecture, better UX, and practical AI features. Reach us at support@flutterdevs.com.


