问题
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