Skip to content
This repository has been archived by the owner on Oct 3, 2024. It is now read-only.

Commit

Permalink
Merge pull request #2 from 4inka/async_fetch
Browse files Browse the repository at this point in the history
Async suggestions fetch
  • Loading branch information
4inka authored Dec 9, 2021
2 parents d7bcf2d + 4a570d1 commit d1f2a11
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 19 deletions.
75 changes: 67 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
# Easy Autocomplete

<a href="https://www.buymeacoffee.com/4inka" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-violet.png" alt="Buy Me A Pizza" style="height: 60px !important; width: 217px !important;" ></a>
<a href="https://www.buymeacoffee.com/4inka" target="_blank">
<img src="https://cdn.buymeacoffee.com/buttons/v2/default-violet.png" alt="Buy Me A Pizza" style="height: 60px !important; width: 217px !important;"/>
</a>


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

Expand All @@ -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';
Expand Down Expand Up @@ -61,7 +66,8 @@ class MyApp extends StatelessWidget {
}
```

<br/>
### 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
Expand Down Expand Up @@ -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)

<br/>
### 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<List<String>> _fetchSuggestions(String searchValue) async {
await Future.delayed(Duration(milliseconds: 750));
List<String> _suggestions = ['Afeganistan', 'Albania', 'Algeria', 'Australia', 'Brazil', 'German', 'Madagascar', 'Mozambique', 'Portugal', 'Zambia'];
List<String> _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<String>` | :heavy_check_mark: | The list of suggestions to be displayed | |
| suggestions | `List<String>` | :x: | The list of suggestions to be displayed | |
| asyncSuggestions | `Future<List<String>> 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 | |
Expand All @@ -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).
Expand Down
55 changes: 47 additions & 8 deletions lib/easy_autocomplete.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> suggestions;
final List<String>? suggestions;
/// Fetches list of suggestions from a Future
final Future<List<String>> Function(String searchValue)? asyncSuggestions;
/// Text editing controller
final TextEditingController? controller;
/// Can be used to decorate the input
Expand All @@ -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,
Expand All @@ -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<EasyAutocomplete> createState() => _EasyAutocompleteState();
Expand All @@ -82,8 +94,11 @@ class _EasyAutocompleteState extends State<EasyAutocomplete> {
final LayerLink _layerLink = LayerLink();
late TextFormField _textFormField;
bool _hasOpenedOverlay = false;
bool _isLoading = false;
OverlayEntry? _overlayEntry;
List<String> _suggestions = [];
Timer? _debounce;
String _previousAsyncSearchText = '';

@override
void initState() {
Expand All @@ -96,6 +111,7 @@ class _EasyAutocompleteState extends State<EasyAutocomplete> {
textCapitalization: widget.textCapitalization,
keyboardType: widget.keyboardType,
cursorColor: widget.cursorColor ?? Colors.blue,
style: widget.inputTextStyle,
onChanged: (value) {
openOverlay();
widget.onChanged!(value);
Expand Down Expand Up @@ -128,6 +144,7 @@ class _EasyAutocompleteState extends State<EasyAutocomplete> {
showWhenUnlinked: false,
offset: Offset(0.0, size.height + 5.0),
child: FilterableList(
loading: _isLoading,
items: _suggestions,
suggestionTextStyle: widget.suggestionTextStyle,
suggestionBackgroundColor: widget.suggestionBackgroundColor,
Expand Down Expand Up @@ -160,10 +177,31 @@ class _EasyAutocompleteState extends State<EasyAutocomplete> {
}
}

void updateSuggestions(String input) {
_suggestions = widget.suggestions.where((element) {
return element.toLowerCase().contains(input.toLowerCase());
}).toList();
Future<void> 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();
}
Expand Down Expand Up @@ -194,6 +232,7 @@ class _EasyAutocompleteState extends State<EasyAutocomplete> {
void dispose() {
if (_overlayEntry != null) _overlayEntry!.dispose();
if (widget.controller != null) widget.controller!.dispose();
if (_debounce != null) _debounce?.cancel();
super.dispose();
}
}
16 changes: 13 additions & 3 deletions lib/widgets/filterable_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,16 @@ class FilterableList extends StatelessWidget {
final double maxListHeight;
final TextStyle suggestionTextStyle;
final Color? suggestionBackgroundColor;
final bool loading;

FilterableList({
required this.items,
required this.onItemTapped,
this.elevation = 5,
this.maxListHeight = 150,
this.suggestionTextStyle = const TextStyle(),
this.suggestionBackgroundColor
this.suggestionBackgroundColor,
this.loading = false
});

@override
Expand All @@ -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(
Expand Down
File renamed without changes
Binary file added preview/preview3.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit d1f2a11

Please sign in to comment.