Google search engine
HomeDevelopersCreating a Smart Form Autofill Feature in Flutter Using Local Data

Creating a Smart Form Autofill Feature in Flutter Using Local Data

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.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments