Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter TextField autocomplete overlay

Tags:

flutter

dart

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.

like image 507
guest3523 Avatar asked Apr 27 '17 08:04

guest3523


People also ask

How do you implement autocomplete in Flutter?

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 do I use TextFormField in Flutter?

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.


3 Answers

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

like image 150
AbdulRahman AlHamali Avatar answered Oct 20 '22 23:10

AbdulRahman AlHamali


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.

Valid XHTML

Valid XHTML

Valid XHTML

Valid XHTML

Valid XHTML

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)
          ],
        ),
      ),
    );
  }
}
like image 21
Ronin Avatar answered Oct 21 '22 01:10

Ronin


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 StreamBuilderfor BLoC pattern.

like image 12
nonybrighto Avatar answered Oct 21 '22 00:10

nonybrighto