Fluent Auto Suggest Box
A highly customizable, performance-optimized auto-suggest/autocomplete widget for Flutter with Fluent UI design. Features include debounced search, LRU caching, keyboard navigation, form validation, advanced search dialog, and BLoC/Cubit state management (similar to smart_pagination).
Features
- Debounced Search - Configurable delay to reduce API calls
- LRU Caching - Intelligent caching with TTL (Time To Live) expiration
- Keyboard Navigation - Full support for Arrow keys, Tab, Escape, and Enter
- Form Validation - Built-in validator support with AutovalidateMode
- Server-Side Search - Async search with
onNoResultsFoundcallback - Loading & Error States - Customizable loading and error handling
- Custom Builders - Full control over item rendering
- Accessibility - Semantic labels and screen reader support
- Recent Searches - Track and display search history
- Advanced Search Dialog - Full-featured search with filters, pagination, and multiple view modes
- BLoC/Cubit Support - State management similar to smart_pagination
- Performance Optimized - Reduced rebuilds, efficient memory usage
Installation
Add this to your pubspec.yaml:
dependencies:
auto_suggest_box: ^0.1.5
Then run:
flutter pub get
Quick Start
Basic Usage
import 'package:auto_suggest_box/auto_suggest_box.dart';
FluentAutoSuggestBox<String>(
items: [
FluentAutoSuggestBoxItem(value: '1', label: 'Apple'),
FluentAutoSuggestBoxItem(value: '2', label: 'Banana'),
FluentAutoSuggestBoxItem(value: '3', label: 'Cherry'),
],
onSelected: (item) {
print('Selected: ${item?.label}');
},
)
With Server Search
FluentAutoSuggestBox<Product>(
items: localProducts,
enableCache: true,
cacheMaxSize: 100,
cacheDuration: Duration(minutes: 30),
debounceDelay: Duration(milliseconds: 300),
onNoResultsFound: (query) async {
// Fetch from server
final response = await api.searchProducts(query);
return response.map((p) => FluentAutoSuggestBoxItem(
value: p,
label: p.name,
subtitle: Text(p.description),
)).toList();
},
onSelected: (item) {
if (item != null) {
navigateToProduct(item.value);
}
},
)
Form Validation
FluentAutoSuggestBox<String>.form(
items: countries,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please select a country';
}
return null;
},
autovalidateMode: AutovalidateMode.onUserInteraction,
onSelected: (item) => setState(() => selectedCountry = item?.value),
)
Cubit/BLoC State Management
This package provides FluentAutoSuggestBoxCubit to manage the state of FluentAutoSuggestBox widget.
Creating a Cubit
final cubit = FluentAutoSuggestBoxCubit<Product>();
Managing Items
// Set items
cubit.setItems([
FluentAutoSuggestBoxItem(value: product1, label: 'iPhone'),
FluentAutoSuggestBoxItem(value: product2, label: 'Samsung'),
FluentAutoSuggestBoxItem(value: product3, label: 'Pixel'),
]);
// Add item
cubit.addItem(FluentAutoSuggestBoxItem(value: product4, label: 'OnePlus'));
// Remove item
cubit.removeItem(item);
cubit.removeItemByValue(product1);
// Clear all items
cubit.clearItems();
Selection
// Select an item
cubit.selectItem(item);
cubit.selectByValue(product1);
cubit.selectByIndex(0);
// Clear selection
cubit.clearSelection();
// Get selected item
final selected = cubit.state.selectedItem;
Loading State
// Show loading
cubit.setLoading(true);
// Load from server
final products = await api.getProducts();
cubit.setItems(products.map((p) =>
FluentAutoSuggestBoxItem(value: p, label: p.name)
).toList());
// Hide loading
cubit.setLoading(false);
Error Handling
try {
cubit.setLoading(true);
final products = await api.getProducts();
cubit.setItems(products);
} catch (e) {
cubit.setError(e);
} finally {
cubit.setLoading(false);
}
Using with FluentAutoSuggestBox
BlocBuilder<FluentAutoSuggestBoxCubit<Product>, FluentAutoSuggestBoxState<Product>>(
bloc: cubit,
builder: (context, state) {
return FluentAutoSuggestBox<Product>(
items: state.items,
enabled: state.isEnabled && !state.isLoading,
onSelected: (item) {
if (item != null) {
cubit.selectItem(item);
}
},
loadingBuilder: state.isLoading
? (context) => const ProgressRing()
: null,
);
},
)
State Properties
final state = cubit.state;
state.items // List of items
state.selectedItem // Currently selected item
state.text // Current text in input
state.isLoading // Loading state
state.error // Error object (if any)
state.hasError // Has error
state.hasSelection // Has selected item
state.isEmpty // Items list is empty
state.itemCount // Number of items
state.isEnabled // Is widget enabled
state.isReadOnly // Is widget read-only
Available Methods
| Method | Description |
|---|---|
setItems(items) |
Set all items |
addItem(item) |
Add single item |
addItems(items) |
Add multiple items |
removeItem(item) |
Remove item |
removeItemByValue(value) |
Remove by value |
clearItems() |
Clear all items |
selectItem(item) |
Select item |
selectByValue(value) |
Select by value |
selectByIndex(index) |
Select by index |
clearSelection() |
Clear selection |
setText(text) |
Set input text |
clearText() |
Clear input text |
setLoading(bool) |
Set loading state |
setError(error) |
Set error |
clearError() |
Clear error |
setEnabled(bool) |
Enable/disable |
setReadOnly(bool) |
Set read-only |
reset() |
Reset to initial state |
clear() |
Clear selection, text, error |
search(query) |
Search in local items |
Using BlocBuilder Directly
BlocBuilder<AutoSuggestCubit<Product>, AutoSuggestState<Product>>(
bloc: productsCubit,
builder: (context, state) {
return switch (state) {
AutoSuggestInitial() => Text('Start typing to search...'),
AutoSuggestLoading(:final query) => CircularProgressIndicator(),
AutoSuggestLoaded(:final items) => ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => Text(items[index].name),
),
AutoSuggestEmpty(:final query) => Text('No results for "$query"'),
AutoSuggestError(:final error) => Text('Error: $error'),
};
},
)
AutoSuggestBlocBuilder (Convenience Widget)
AutoSuggestBlocBuilder<Product>(
cubit: productsCubit,
onInitial: (context) => Text('Ready to search'),
onLoading: (context, query, previousItems) => ProgressRing(),
onLoaded: (context, items, query) => ProductList(items: items),
onEmpty: (context, query) => Text('No products found'),
onError: (context, error, query, previousItems) => ErrorWidget(error),
)
State Classes
| State | Properties | Description |
|---|---|---|
AutoSuggestInitial |
- | Initial state before any search |
AutoSuggestLoading |
query, previousItems, isLoadingMore |
Loading state |
AutoSuggestLoaded |
items, query, fetchedAt, dataExpiredAt |
Success with data |
AutoSuggestEmpty |
query, searchedAt |
No results found |
AutoSuggestError |
error, query, previousItems |
Error state |
Data Expiration (like smart_pagination)
// Check if data is expired
if (cubit.isDataExpired) {
await cubit.refresh();
}
// Or use automatic check
await cubit.checkAndRefreshIfExpired();
// Access data age
final age = cubit.dataAge; // Duration since last fetch
Statistics
final stats = cubit.stats;
print('Cache hits: ${stats['cacheHits']}');
print('Cache misses: ${stats['cacheMisses']}');
print('Hit rate: ${(stats['cacheHitRate'] * 100).toStringAsFixed(1)}%');
API Reference
FluentAutoSuggestBox
The main widget for creating auto-suggest input fields.
| Parameter | Type | Default | Description |
|---|---|---|---|
items |
List<FluentAutoSuggestBoxItem<T>> |
required | List of suggestion items |
controller |
TextEditingController? |
null | Text controller for the input field |
autoSuggestController |
AutoSuggestController<T>? |
null | Controller for managing state |
onChanged |
OnTextChanged<T>? |
null | Callback when text changes |
onSelected |
ValueChanged<FluentAutoSuggestBoxItem<T>?>? |
null | Callback when item is selected |
itemBuilder |
ItemBuilder<T>? |
null | Custom widget builder for items |
noResultsFoundBuilder |
WidgetBuilder? |
null | Widget shown when no results |
loadingBuilder |
WidgetBuilder? |
null | Widget shown while loading |
sorter |
ItemSorter<T>? |
defaultItemSorter | Custom sorting function |
enableCache |
bool |
true | Enable result caching |
cacheMaxSize |
int |
100 | Maximum cache entries |
cacheDuration |
Duration |
30 minutes | Cache TTL |
debounceDelay |
Duration |
300ms | Debounce delay for search |
minSearchLength |
int |
2 | Minimum characters to trigger search |
maxPopupHeight |
double |
380.0 | Maximum height of suggestion popup |
direction |
AutoSuggestBoxDirection |
below | Popup direction (above/below) |
validator |
FormFieldValidator<String>? |
null | Form validation function |
autovalidateMode |
AutovalidateMode |
disabled | Validation mode |
FluentAutoSuggestBoxItem
Represents a single suggestion item.
FluentAutoSuggestBoxItem<T>({
required T value, // The actual data value
required String label, // Display text
Widget? child, // Custom widget (overrides label)
Widget? subtitle, // Secondary text/widget
bool enabled = true, // Whether item is selectable
String? semanticLabel, // Accessibility label
})
AutoSuggestController
Controller for managing search state and metrics.
final controller = AutoSuggestController<String>(
debounceDelay: Duration(milliseconds: 300),
minSearchLength: 2,
maxRecentSearches: 10,
enableRecentSearches: true,
);
// Access state
print(controller.searchQuery);
print(controller.isLoading);
print(controller.recentSearches);
// Get statistics
final stats = controller.getStats();
print('Success rate: ${stats['successRate']}');
// Clean up
controller.dispose();
SearchResultsCache
LRU cache with automatic expiration.
final cache = SearchResultsCache<Product>(
maxSize: 100,
maxAge: Duration(minutes: 30),
enablePrefixMatching: true,
);
// Cache operations
cache.set('query', results);
final cached = cache.get('query');
cache.clear();
// Get statistics
final stats = cache.getStats();
print('Hit rate: ${(stats.hitRate * 100).toStringAsFixed(1)}%');
Advanced Search Dialog
For complex search requirements, use the AdvancedSearchDialog:
// Single selection
final result = await AdvancedSearchDialog.show<Product>(
context: context,
items: products,
onSearch: (query, filters) async {
return await api.search(query, filters: filters);
},
config: AdvancedSearchConfig(
title: 'Find Product',
searchHint: 'Search by name or SKU...',
showFilters: true,
showStats: true,
viewMode: AdvancedSearchViewMode.grid,
keyboardShortcut: SingleActivator(LogicalKeyboardKey.f3),
),
);
// Multi-selection
final results = await AdvancedSearchDialog.showMultiSelect<Product>(
context: context,
items: products,
onSearch: (query, filters) async => await api.search(query),
maxSelections: 5,
);
View Modes
| Mode | Description |
|---|---|
list |
Traditional list view with details |
grid |
Card-based grid layout |
compact |
Condensed single-line items |
Configuration Options
AdvancedSearchConfig(
title: 'Search', // Dialog title
searchHint: 'Type to search...', // Placeholder text
keyboardShortcut: SingleActivator(LogicalKeyboardKey.f3),
showFilters: true, // Show filter panel
showStats: true, // Show result statistics
viewMode: AdvancedSearchViewMode.list,
enableViewModeSwitch: true, // Allow mode switching
resultsPerPage: 20, // Pagination size
enablePagination: false, // Enable pagination
enableAnimations: true, // Enable animations
)
Custom Builders
Custom Item Builder
FluentAutoSuggestBox<Product>(
items: products,
itemBuilder: (context, item) {
return ListTile(
leading: Image.network(item.value.imageUrl),
title: Text(item.label),
subtitle: Text('\$${item.value.price}'),
trailing: Icon(Icons.chevron_right),
);
},
)
Custom Sorter
FluentAutoSuggestBox<Product>(
items: products,
sorter: (query, items) {
return items.where((item) {
// Custom matching logic
return item.label.toLowerCase().contains(query.toLowerCase()) ||
item.value.sku.contains(query);
}).toSet();
},
)
Custom Loading Builder
FluentAutoSuggestBox<String>(
items: items,
loadingBuilder: (context) {
return Padding(
padding: EdgeInsets.all(16),
child: Row(
children: [
CircularProgressIndicator(),
SizedBox(width: 16),
Text('Searching...'),
],
),
);
},
)
Keyboard Navigation
| Key | Action |
|---|---|
Arrow Down |
Select next item |
Arrow Up |
Select previous item |
Enter |
Confirm selection |
Tab |
Move to next field |
Shift+Tab |
Move to previous field |
Escape |
Close suggestions |
F3 |
Open advanced search (if enabled) |
Performance Tips
- Enable Caching - Set
enableCache: truefor repeated searches - Adjust Debounce - Increase
debounceDelayfor slow APIs - Set Min Length - Use
minSearchLengthto prevent unnecessary searches - Use Prefix Matching - Cache uses prefix matching by default
- Limit Results - Return limited results from
onNoResultsFound
FluentAutoSuggestBox<String>(
items: items,
enableCache: true,
cacheMaxSize: 50,
cacheDuration: Duration(minutes: 15),
debounceDelay: Duration(milliseconds: 500),
minSearchLength: 3,
)
Theming
Custom Theme
AdvancedSearchConfig(
theme: AdvancedSearchTheme(
primaryColor: Colors.blue,
backgroundColor: Colors.white,
cardColor: Colors.grey[50],
selectedItemColor: Colors.blue.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
spacing: 16.0,
elevation: 8.0,
),
)
Custom Icons
AdvancedSearchConfig(
icons: AdvancedSearchIcons(
search: FluentIcons.search,
clear: FluentIcons.clear,
filter: FluentIcons.filter,
viewList: FluentIcons.view_list,
viewGrid: FluentIcons.grid_view_medium,
),
)
Example App
See the /example folder for a complete sample application demonstrating:
- Basic autocomplete
- Server-side search with caching
- Form validation
- Custom builders
- Advanced search dialog
- Multiple view modes
- Keyboard shortcuts
Run the example:
cd example
flutter run
Requirements
- Flutter >= 1.17.0
- Dart SDK >= 3.10.3
Dependencies
- fluent_ui ^4.13.0
- flutter_bloc ^8.1.6
- equatable ^2.0.5
- gap ^3.0.1
Roadmap
xCubit/BLoC state management integration (similar to smart_pagination)RTL language support improvementsMore animation optionsVoice search supportGrouped suggestionsInline suggestions (ghost text)Pagination support for large datasets
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Support
If you find this package helpful, please give it a star on GitHub!
Libraries
- advanced_auto_suggest/auto_suggest_advanced
- auto_suggest/auto_suggest
- auto_suggest/auto_suggest_cache
- auto_suggest/auto_suggest_controller
- auto_suggest/auto_suggest_item
- auto_suggest/auto_suggest_overlay
- auto_suggest/auto_suggest_theme
- auto_suggest_box
- A highly customizable, performance-optimized auto-suggest/autocomplete widget for Flutter with Fluent UI design.
- bloc/auto_suggest_box_cubit
- bloc/auto_suggest_cubit
- bloc/auto_suggest_state
- bloc/bloc
- BLoC/Cubit state management for auto_suggest_box
- bloc/bloc_auto_suggest_box
- common/accent_color_extension
- common/text
- common/text_form
- common/validator_form_field