I am struggling to create TextField with autocomplete overlay. I have form with TextFields and I want to display suggestions based on what is typed in TextField.
Something like this: TextField autocomplete I am not sure what the hierarchy of widgets should look like to achieve displaying the suggestions box above other widgets. Should I use Stack widget, OverflowBox widget or something else?
Any help by hierarchy example appreciated.
In Flutter, that can be done by using Autocomplete widget. It's a widget that allows the user to type on a text input and choose from a list of options. In this tutorial, I am going to show you how to use the widget, including how to set the options, customize the TextField , and handle option selected event.
How to Handle Input Data In TextFormField In Flutter Using Controller. To handle user input in TextFormField, create an object of TextEditingController class. Create a TextEditingController object like below and assign it to the controller property of TextFormField. Its object will hold the input data.
I have implemented a package, flutter_typeahead to do exactly that. In this package, I use Overlay.of(context).insert, which allows me to insert the suggestions list in the Overlay, making it float on top of all other widgets. I also wrote this article to explain how to do this in detail
I implemented for my app using Stack. TextFormField in one container and ListTiles in another container and overlaying the listtile as user types on the container of text input field. You could check out my app.
The following sample app uses suggestions as user type from api and display in list which user can select by tapping.
Code Sample:
import 'package:flutter/material.dart';
import 'package:search_suggestions/suggestions_page.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Suggestions Demo',
debugShowCheckedModeBanner: false,
theme: new ThemeData(
brightness: Brightness.light,
primarySwatch: Colors.orange,
),
home: new SuggestionsPage(),
);
}
}
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:flutter/material.dart';
import 'dart:io';
import 'dart:async';
class SuggestionsPage extends StatefulWidget {
SuggestionsPage({Key key}) : super(key: key);
@override
_SuggestionsPageState createState() => new _SuggestionsPageState();
}
class _SuggestionsPageState extends State<SuggestionsPage> {
static const JsonCodec JSON = const JsonCodec();
final key = new GlobalKey<ScaffoldState>();
final TextEditingController _searchQueryController =
new TextEditingController();
final FocusNode _focusNode = new FocusNode();
bool _isSearching = true;
String _searchText = "";
List<String> _searchList = List();
bool _onTap = false;
int _onTapTextLength = 0;
_SuggestionsPageState() {
_searchQueryController.addListener(() {
if (_searchQueryController.text.isEmpty) {
setState(() {
_isSearching = false;
_searchText = "";
_searchList = List();
});
} else {
setState(() {
_isSearching = true;
_searchText = _searchQueryController.text;
_onTap = _onTapTextLength == _searchText.length;
});
}
});
}
@override
void initState() {
super.initState();
_isSearching = false;
}
@override
Widget build(BuildContext context) {
return new Scaffold(
key: key,
appBar: buildAppbar(context),
body: buildBody(context),
);
}
Widget getFutureWidget() {
return new FutureBuilder(
future: _buildSearchList(),
initialData: List<ListTile>(),
builder:
(BuildContext context, AsyncSnapshot<List<ListTile>> childItems) {
return new Container(
color: Colors.white,
height: getChildren(childItems).length * 48.0,
width: MediaQuery.of(context).size.width,
child: new ListView(
// padding: new EdgeInsets.only(left: 50.0),
children: childItems.data.isNotEmpty
? ListTile
.divideTiles(
context: context, tiles: getChildren(childItems))
.toList()
: List(),
),
);
});
}
List<ListTile> getChildren(AsyncSnapshot<List<ListTile>> childItems) {
if (_onTap && _searchText.length != _onTapTextLength) _onTap = false;
List<ListTile> childrenList =
_isSearching && !_onTap ? childItems.data : List();
return childrenList;
}
ListTile _getListTile(String suggestedPhrase) {
return new ListTile(
dense: true,
title: new Text(
suggestedPhrase,
style: Theme.of(context).textTheme.body2,
),
onTap: () {
setState(() {
_onTap = true;
_isSearching = false;
_onTapTextLength = suggestedPhrase.length;
_searchQueryController.text = suggestedPhrase;
});
_searchQueryController.selection = TextSelection
.fromPosition(new TextPosition(offset: suggestedPhrase.length));
},
);
}
Future<List<ListTile>> _buildSearchList() async {
if (_searchText.isEmpty) {
_searchList = List();
return List();
} else {
_searchList = await _getSuggestion(_searchText) ?? List();
// ..add(_searchText);
List<ListTile> childItems = new List();
for (var value in _searchList) {
if (!(value.contains(" ") && value.split(" ").length > 2)) {
childItems.add(_getListTile(value));
}
}
return childItems;
}
}
Future<List<String>> _getSuggestion(String hintText) async {
String url = "SOME_TEST_API?s=$hintText&max=4";
var response =
await http.get(Uri.parse(url), headers: {"Accept": "application/json"});
List decode = JSON.decode(response.body);
if (response.statusCode != HttpStatus.OK || decode.length == 0) {
return null;
}
List<String> suggestedWords = new List();
if (decode.length == 0) return null;
decode.forEach((f) => suggestedWords.add(f["word"]));
// String data = decode[0]["word"];
return suggestedWords;
}
Widget buildAppbar(BuildContext context) {
return new AppBar(
title: new Text('Suggestions Demo'),
);
}
Widget buildBody(BuildContext context) {
return new SafeArea(
top: false,
bottom: false,
child: new SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: new Stack(
children: <Widget>[
new Column(
children: <Widget>[
Container(
height: MediaQuery.of(context).size.height,
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
const SizedBox(height: 80.0),
new TextFormField(
controller: _searchQueryController,
focusNode: _focusNode,
onFieldSubmitted: (String value) {
print("$value submitted");
setState(() {
_searchQueryController.text = value;
_onTap = true;
});
},
onSaved: (String value) => print("$value saved"),
decoration: const InputDecoration(
border: const UnderlineInputBorder(),
filled: true,
icon: const Icon(Icons.search),
hintText: 'Type two words with space',
labelText: 'Seach words *',
),
),
const SizedBox(height: 40.0),
new Center(
child: new RaisedButton(
color: Colors.orangeAccent,
onPressed: () => print("Pressed"),
child: const Text(
' Search ',
style: const TextStyle(fontSize: 18.0),
)),
),
const SizedBox(height: 200.0),
],
),
),
],
),
new Container(
alignment: Alignment.topCenter,
padding: new EdgeInsets.only(
// top: MediaQuery.of(context).size.height * .18,
top: 136.0,
right: 0.0,
left: 38.0),
child: _isSearching && (!_onTap) ? getFutureWidget() : null)
],
),
),
);
}
}
You can make use of autocomplete_textfield library to achieve that.
Basic Usage
...
SimpleAutoCompleteTextField(
key: key,
suggestions: [
"Apple",
"Armidillo",
"Actual",
"Actuary",
"America",
"Argentina",
"Australia",
"Antarctica",
"Blueberry",],
decoration: InputDecoration(
filled: true,
fillColor: Colors.black12,
hintText: 'Dictionary'
),
),
...
You can get more examples here.
Another Option
You can also make use of flutter_typeahead
This worked better for me when working with StreamBuilder
for BLoC pattern.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With