diff --git a/README.md b/README.md
index 051c165..9917430 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,20 @@
# Easy Autocomplete
-
+
+
+
A Flutter plugin to handle input autocomplete suggestions
## Preview
-![Preview](https://raw.githubusercontent.com/4inka/flutter_easy_autocomplete/main/preview/preview.gif)
+![Preview](https://raw.githubusercontent.com/4inka/flutter_easy_autocomplete/main/preview/preview1.gif)
## ToDo
* Add validation functionality
-* Add asynchronous suggestions fetch
* Add possibility to show empty message when no suggestion is found
+## Done
+* Add asynchronous suggestions fetch
## Usage
@@ -20,10 +23,12 @@ In the `pubspec.yaml` of your flutter project, add the following dependency:
``` yaml
dependencies:
...
- easy_autocomplete: ^1.1.0
+ easy_autocomplete: ^1.2.0
```
-You can create a simple autocomplete input widget with the following example:
+### Basic example
+
+You can create a simple autocomplete input widget as shown in first preview with the following example:
``` dart
import 'package:easy_autocomplete/easy_autocomplete.dart';
@@ -61,7 +66,8 @@ class MyApp extends StatelessWidget {
}
```
-
+### Example with customized style
+
You can customize other aspects of the autocomplete widget such as the suggestions text style, background color and others as shown in example below:
``` dart
@@ -127,12 +133,63 @@ The above example will generate something like below preview:
![Preview](https://raw.githubusercontent.com/4inka/flutter_easy_autocomplete/main/preview/preview2.gif)
-
+### Example with asynchronous data fetch
+
+To create a autocomplete field that fetches data asynchronously you will need to use `asyncSuggestions` instead of `suggestions`
+``` dart
+import 'package:easy_autocomplete/easy_autocomplete.dart';
+import 'package:flutter/material.dart';
+
+void main() {
+ runApp(MyApp());
+}
+
+class MyApp extends StatelessWidget {
+ Future> _fetchSuggestions(String searchValue) async {
+ await Future.delayed(Duration(milliseconds: 750));
+ List _suggestions = ['Afeganistan', 'Albania', 'Algeria', 'Australia', 'Brazil', 'German', 'Madagascar', 'Mozambique', 'Portugal', 'Zambia'];
+ List _filteredSuggestions = _suggestions.where((element) {
+ return element.toLowerCase().contains(searchValue.toLowerCase());
+ }).toList();
+ return _filteredSuggestions;
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ title: 'Example',
+ theme: ThemeData(
+ primarySwatch: Colors.blue,
+ ),
+ home: SafeArea(
+ child: Scaffold(
+ appBar: AppBar(
+ title: Text('Example')
+ ),
+ body: Container(
+ padding: EdgeInsets.all(10),
+ alignment: Alignment.center,
+ child: EasyAutocomplete(
+ asyncSuggestions: (searchValue) async => _fetchSuggestions(searchValue),
+ onChanged: (value) => print(value)
+ )
+ )
+ )
+ )
+ );
+ }
+}
+```
+
+The above example will generate something like below preview:
+
+![Preview](https://raw.githubusercontent.com/4inka/flutter_easy_autocomplete/main/preview/preview3.gif)
## API
| Attribute | Type | Required | Description | Default value |
|:---|:---|:---:|:---|:---|
-| suggestions | `List` | :heavy_check_mark: | The list of suggestions to be displayed | |
+| suggestions | `List` | :x: | The list of suggestions to be displayed | |
+| asyncSuggestions | `Future> Function(String)` | :x: | Fetches list of suggestions from a Future | |
| controller | `TextEditingController` | :x: | Text editing controller | |
| decoration | `InputDecoration` | :x: | Can be used to decorate the input | |
| onChanged | `Function(String)` | :x: | Function that handles the changes to the input | |
@@ -142,8 +199,10 @@ The above example will generate something like below preview:
| autofocus | `bool` | :x: | Determines if should gain focus on screen open | false |
| keyboardType | `TextInputType` | :x: | Can be used to set different keyboardTypes to your field | TextInputType.text |
| cursorColor | `Color` | :x: | Can be used to set a custom color to the input cursor | Colors.blue |
+| inputTextStyle | `TextStyle` | :x: | Can be used to set custom style to the suggestions textfield | |
| suggestionTextStyle | `TextStyle` | :x: | Can be used to set custom style to the suggestions list text | |
| suggestionBackgroundColor | `Color` | :x: | Can be used to set custom background color to suggestions list | |
+| debounceDuration | `Duration` | :x: | Used to set the debounce time for async data fetch | Duration(milliseconds: 400) |
## Issues & Suggestions
If you encounter any issue you or want to leave a suggestion you can do it by filling an [issue](https://github.com/4inka/flutter_easy_autocomplete/issues).
diff --git a/lib/easy_autocomplete.dart b/lib/easy_autocomplete.dart
index c675b1e..d478d7d 100644
--- a/lib/easy_autocomplete.dart
+++ b/lib/easy_autocomplete.dart
@@ -27,13 +27,17 @@
library easy_autocomplete;
+import 'dart:async';
+
import 'package:easy_autocomplete/widgets/filterable_list.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class EasyAutocomplete extends StatefulWidget {
/// The list of suggestions to be displayed
- final List suggestions;
+ final List? suggestions;
+ /// Fetches list of suggestions from a Future
+ final Future> Function(String searchValue)? asyncSuggestions;
/// Text editing controller
final TextEditingController? controller;
/// Can be used to decorate the input
@@ -52,14 +56,19 @@ class EasyAutocomplete extends StatefulWidget {
final TextInputType keyboardType;
/// Can be used to set a custom color to the input cursor
final Color? cursorColor;
+ /// Can be used to set custom style to the suggestions textfield
+ final TextStyle inputTextStyle;
/// Can be used to set custom style to the suggestions list text
final TextStyle suggestionTextStyle;
/// Can be used to set custom background color to suggestions list
final Color? suggestionBackgroundColor;
+ /// Used to set the debounce time for async data fetch
+ final Duration debounceDuration;
/// Creates a autocomplete widget to help you manage your suggestions
EasyAutocomplete({
- required this.suggestions,
+ this.suggestions,
+ this.asyncSuggestions,
this.controller,
this.decoration = const InputDecoration(),
this.onChanged,
@@ -69,10 +78,13 @@ class EasyAutocomplete extends StatefulWidget {
this.textCapitalization = TextCapitalization.sentences,
this.keyboardType = TextInputType.text,
this.cursorColor,
+ this.inputTextStyle = const TextStyle(),
this.suggestionTextStyle = const TextStyle(),
- this.suggestionBackgroundColor
+ this.suggestionBackgroundColor,
+ this.debounceDuration = const Duration(milliseconds: 400)
}) : assert(onChanged != null || controller != null, 'onChanged and controller parameters cannot be both null at the same time'),
- assert(!(controller != null && initialValue != null), 'controller and initialValue cannot be used at the same time');
+ assert(!(controller != null && initialValue != null), 'controller and initialValue cannot be used at the same time'),
+ assert(suggestions != null && asyncSuggestions == null || suggestions == null && asyncSuggestions != null, 'suggestions and asyncSuggestions cannot be both null or have values at the same time');
@override
State createState() => _EasyAutocompleteState();
@@ -82,8 +94,11 @@ class _EasyAutocompleteState extends State {
final LayerLink _layerLink = LayerLink();
late TextFormField _textFormField;
bool _hasOpenedOverlay = false;
+ bool _isLoading = false;
OverlayEntry? _overlayEntry;
List _suggestions = [];
+ Timer? _debounce;
+ String _previousAsyncSearchText = '';
@override
void initState() {
@@ -96,6 +111,7 @@ class _EasyAutocompleteState extends State {
textCapitalization: widget.textCapitalization,
keyboardType: widget.keyboardType,
cursorColor: widget.cursorColor ?? Colors.blue,
+ style: widget.inputTextStyle,
onChanged: (value) {
openOverlay();
widget.onChanged!(value);
@@ -128,6 +144,7 @@ class _EasyAutocompleteState extends State {
showWhenUnlinked: false,
offset: Offset(0.0, size.height + 5.0),
child: FilterableList(
+ loading: _isLoading,
items: _suggestions,
suggestionTextStyle: widget.suggestionTextStyle,
suggestionBackgroundColor: widget.suggestionBackgroundColor,
@@ -160,10 +177,31 @@ class _EasyAutocompleteState extends State {
}
}
- void updateSuggestions(String input) {
- _suggestions = widget.suggestions.where((element) {
- return element.toLowerCase().contains(input.toLowerCase());
- }).toList();
+ Future updateSuggestions(String input) async {
+ rebuildOverlay();
+ if (widget.suggestions != null) {
+ _suggestions = widget.suggestions!.where((element) {
+ return element.toLowerCase().contains(input.toLowerCase());
+ }).toList();
+ rebuildOverlay();
+ }
+ else if (widget.asyncSuggestions != null) {
+ setState(() => _isLoading = true);
+ if (_debounce != null && _debounce!.isActive) _debounce!.cancel();
+ _debounce = Timer(widget.debounceDuration, () async {
+ if (_previousAsyncSearchText != input || _previousAsyncSearchText.isEmpty || input.isEmpty) {
+ _suggestions = await widget.asyncSuggestions!(input);
+ setState(() {
+ _isLoading = false;
+ _previousAsyncSearchText = input;
+ });
+ rebuildOverlay();
+ }
+ });
+ }
+ }
+
+ void rebuildOverlay() {
if(_overlayEntry != null) {
_overlayEntry!.markNeedsBuild();
}
@@ -194,6 +232,7 @@ class _EasyAutocompleteState extends State {
void dispose() {
if (_overlayEntry != null) _overlayEntry!.dispose();
if (widget.controller != null) widget.controller!.dispose();
+ if (_debounce != null) _debounce?.cancel();
super.dispose();
}
}
\ No newline at end of file
diff --git a/lib/widgets/filterable_list.dart b/lib/widgets/filterable_list.dart
index 83b61c1..9fed908 100644
--- a/lib/widgets/filterable_list.dart
+++ b/lib/widgets/filterable_list.dart
@@ -34,6 +34,7 @@ class FilterableList extends StatelessWidget {
final double maxListHeight;
final TextStyle suggestionTextStyle;
final Color? suggestionBackgroundColor;
+ final bool loading;
FilterableList({
required this.items,
@@ -41,7 +42,8 @@ class FilterableList extends StatelessWidget {
this.elevation = 5,
this.maxListHeight = 150,
this.suggestionTextStyle = const TextStyle(),
- this.suggestionBackgroundColor
+ this.suggestionBackgroundColor,
+ this.loading = false
});
@override
@@ -53,12 +55,20 @@ class FilterableList extends StatelessWidget {
maxHeight: maxListHeight
),
child: Visibility(
- visible: items.isNotEmpty,
+ visible: items.isNotEmpty || loading,
child: ListView.builder(
shrinkWrap: true,
padding: EdgeInsets.zero,
- itemCount: items.length,
+ itemCount: loading ? 1 : items.length,
itemBuilder: (context, index) {
+ if (loading) {
+ return Container(
+ alignment: Alignment.center,
+ padding: EdgeInsets.all(10),
+ child: CircularProgressIndicator()
+ );
+ }
+
return Material(
color: suggestionBackgroundColor ?? Colors.transparent,
child: InkWell(
diff --git a/preview/preview.gif b/preview/preview1.gif
similarity index 100%
rename from preview/preview.gif
rename to preview/preview1.gif
diff --git a/preview/preview3.gif b/preview/preview3.gif
new file mode 100644
index 0000000..78b866e
Binary files /dev/null and b/preview/preview3.gif differ