Using overlay to build autocomplete for flutter web

青春壹個敷衍的年華 提交于 2020-06-01 07:40:07

问题


After trying plugins that were designed to make this easier. I tried to make my own, however, I am unable to ascertain a way to do this without causing breakages or multiple listeners to become active.

Goal: 1) User types it. Above 0 length, it will show the data (after briefly saying Loading..) 2) Once user clicks their selection on Overlay, it uses that data and puts that into the textField 3) User can begin typing again, or remove current data and process can repeat

Bugs: 1) Overlay becoming null can cause me to have an issue with closing the overlay once complete 2) Multiple listeners come into place presenting issues similar to below:

Oklahoma City
controllerText : Oklahoma City selectedText: Oklahoma City
in snapshot.hasData Oklahoma City
in snapshot.hasData Oklahoma City
in snapshot.hasData Oklahoma City
in snapshot.hasData Oklahoma City

Current code:

import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

Future<dynamic> fetchAlbum(String query) async {
  if (query.length > 0) {
    final response = await http.get(
      'https://cors-anywhere.herokuapp.com/https://en.wikipedia.org/w/api.php?action=opensearch&search=$query&limit=20&format=json',
    );

    if (response.statusCode == 200) {
      // If the server did return a 200 OK response,
      // then parse the JSON.
      return List<dynamic>.from(json.decode(response.body).map((x) => x));
    } else {
      // If the server did not return a 200 OK response,
      // then throw an exception.
      throw Exception('Failed to fetch items');
    }
  }
}

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            CountriesField(),
            Text('Example text'),
          ],
        ),
      ),
    );
  }
}

class CountriesField extends StatefulWidget {
  @override
  _CountriesFieldState createState() => _CountriesFieldState();
}

class _CountriesFieldState extends State<CountriesField> {
  String _selectedText;
  Future<dynamic> _futureAlbum;

  // Create a text controller and use it to retrieve the current value
  // of the TextField.
  final TextEditingController _controller = TextEditingController();

  final FocusNode _focusNode = FocusNode();

  OverlayEntry _overlayEntry;

  final LayerLink _layerLink = LayerLink();

  bool _closed = false;

  @override
  void initState() {
    super.initState();
    _controller.addListener(() {
      print(
          'controllerText : ${_controller.text} selectedText: ${_selectedText}');
      if (_controller.text != _selectedText) {
        if (!_closed && this._overlayEntry != null) {
          this._overlayEntry.remove();
          _closed = false;
        }
        this._overlayEntry = this._createOverlayEntry();
        Overlay.of(context).insert(this._overlayEntry);
      }
      setState(() {
        _futureAlbum = fetchAlbum(_controller.text);
      });
    });
    _focusNode.addListener(() {
      if (!_focusNode.hasFocus) {
        _closed = true;
        this._overlayEntry.remove();
      }
    });
  }

  OverlayEntry _createOverlayEntry() {
    RenderBox renderBox = context.findRenderObject();
    var size = renderBox.size;

    return OverlayEntry(
      builder: (context) => Positioned(
        width: size.width,
        child: CompositedTransformFollower(
          link: this._layerLink,
          showWhenUnlinked: false,
          offset: Offset(0.0, size.height + 5.0),
          child: Material(
            elevation: 4.0,
            child: FutureBuilder<dynamic>(
              future: _futureAlbum,
              builder: (context, snapshot) {
                if (snapshot.hasData) {
                  print('in snapshot.hasData ${_controller.text}');
                  List<dynamic> b = snapshot.data[1];
                  if (b.length > 0) {
                    return ListView.builder(
                      padding: EdgeInsets.zero,
                      shrinkWrap: true,
                      itemCount: b.length,
                      itemBuilder: (context, index) {
                        String text = b[index];
                        return GestureDetector(
                          onPanDown: (_) {
                            print(text);
                            _selectedText = text;
                            _controller.text = text;
                          },
                          child: ListTile(
                            title: Text(text),
                          ),
                        );
                      },
                    );
                  } else {
                    return Text('No items found');
                  }
                } else if (snapshot.hasError) {
                  return Text("${snapshot.error}");
                }
                // By default, show a loading spinner.
                return Text('Loading...');
              },
            ),
          ),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return CompositedTransformTarget(
      link: this._layerLink,
      child: TextFormField(
        focusNode: this._focusNode,
        controller: _controller,
        decoration: InputDecoration(labelText: 'Country'),
      ),
    );
  }
}

来源:https://stackoverflow.com/questions/62024669/using-overlay-to-build-autocomplete-for-flutter-web

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!