我需要使用默认Flutter的 SearchPage 来获取Google地方信息搜索建议,每当用户开始输入内容时,我都需要提供自动填充建议,并且我会使用FutureBuilder
异步实现此问题,我需要在500毫秒或更长时间内消除搜索请求的发送,而不是在用户仍在键入时浪费大量请求
总结我到目前为止所做的事情:
1)在我的小部件中,我叫
showSearch(context: context, delegate: _delegate);
2)我的代表看起来像这样:
class _LocationSearchDelegate extends SearchDelegate<Suggestion> {
@override
List<Widget> buildActions(BuildContext context) {
return <Widget>[
IconButton(
tooltip: 'Clear',
icon: const Icon(Icons.clear),
onPressed: () {
query = '';
showSuggestions(context);
},
)
];
}
@override
Widget buildLeading(BuildContext context) => IconButton(
tooltip: 'Back',
icon: AnimatedIcon(
icon: AnimatedIcons.menu_arrow,
progress: transitionAnimation,
),
onPressed: () {
close(context, null);
},
);
@override
Widget buildResults(BuildContext context) {
return FutureBuilder<List<Suggestion>>(
future: GooglePlaces.getInstance().getAutocompleteSuggestions(query),
builder: (BuildContext context, AsyncSnapshot<List<Suggestion>> suggestions) {
if (!suggestions.hasData) {
return Text('No results');
}
return buildLocationSuggestions(suggestions.data);
},
);
}
@override
Widget buildSuggestions(BuildContext context) {
return buildResults(context);
}
Widget buildLocationSuggestions(List<Suggestion> suggestions) {
return ListView.builder(
itemBuilder: (context, index) => ListTile(
leading: Icon(Icons.location_on),
title: Text(suggestions[index].text),
onTap: () {
showResults(context);
},
),
itemCount: suggestions.length,
);
}
}
3-我需要进行限制/反跳搜索,直到过去xxx毫秒
我有一个主意,是否有一种简单的方法可以将FutureBuilder转换为Stream并使用Stream Builder(我在一些文章中看到它支持反跳)?
**我知道有一些TypeAhead
这样的第三方自动完成小部件(从头开始)正在执行此操作,但是我现在不想使用它。
答案 0 :(得分:2)
更新:我为此制作了一个程序包,可用于回调,期货和/或流。 https://pub.dartlang.org/packages/simple_observable。使用它会简化下面描述的两种方法,尤其是基于流的方法,因为不需要引入新的类。这是一个dartpad示例https://dartpad.dartlang.org/e4e9c07dc320ec400a59827fff66bb49。
至少有两种方法可以执行此操作,一种基于Future
的方法和一种基于Stream
的方法。自内置防反弹功能以来,类似的问题已指向使用Streams,但让我们看看这两种方法。
基于未来的方法
Future
本身是不可取消的,但是它们使用的基础Timer
是可以取消的。这是一个简单的类,它使用回调而不是Future来实现基本的去抖动功能。
class Debouncer<T> {
Debouncer(this.duration, this.onValue);
final Duration duration;
void Function(T value) onValue;
T _value;
Timer _timer;
T get value => _value;
set value(T val) {
_value = val;
_timer?.cancel();
_timer = Timer(duration, () => onValue(_value));
}
}
然后使用它(兼容DartPad):
import 'dart:async';
void main() {
final debouncer = Debouncer<String>(Duration(milliseconds: 250), print);
debouncer.value = '';
final timer = Timer.periodic(Duration(milliseconds: 200), (_) {
debouncer.value += 'x';
});
/// prints "xxxxx" after 1250ms.
Future.delayed(Duration(milliseconds: 1000)).then((_) => timer.cancel());
}
现在要将回调变成Future,请使用Completer
。这是一个示例,该示例将对Google API的List<Suggestion>
调用进行反抖动。
void main() {
final completer = Completer<List<Suggestion>>();
final debouncer = Debouncer<String>(Duration(milliseconds: 250), (value) async {
completer.complete(await GooglePlaces.getInstance().getAutocompleteSuggestions(value));
});
/// Using with a FutureBuilder.
@override
Widget build(BuildContext context) {
return FutureBuilder<List<Suggestion>>(
future: completer.future,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
} else {
return Center(child: CircularProgressIndicator());
}
},
);
}
}
基于流的方法
由于所讨论的数据来自Future而不是Stream,因此我们必须设置一个类来处理查询输入和建议输出。幸运的是,它可以自然地消除输入流的反跳。
class SuggestionsController {
SuggestionsController(this.duration) {
_queryController.stream
.transform(DebounceStreamTransformer(duration))
.listen((query) async {
_suggestions.add(
await GooglePlaces.getInstance().getAutocompleteSuggestions(query));
});
}
final Duration duration;
final _queryController = StreamController<String>();
final _suggestions = BehaviorSubject<List<Suggestion>>();
Sink<String> get query => _queryController.sink;
Stream<List<Suggestion>> get suggestions => _suggestions.stream;
void dispose() {
_queryController.close();
_suggestions.close();
}
}
要在Flutter中使用此控制器类,让我们创建一个StatefulWidget来管理控制器的状态。这部分包括对函数buildLocationSuggestions()
的调用。
class SuggestionsWidget extends StatefulWidget {
_SuggestionsWidgetState createState() => _SuggestionsWidgetState();
}
class _SuggestionsWidgetState extends State<SuggestionsWidget> {
final duration = Duration(milliseconds: 250);
SuggestionsController controller;
@override
Widget build(BuildContext context) {
return StreamBuilder<List<Suggestion>>(
stream: controller.suggestions,
builder: (context, snapshot) {
if (snapshot.hasData) {
return buildLocationSuggestions(snapshot.data);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
} else {
return Center(child: CircularProgressIndicator());
}
},
);
}
@override
void initState() {
super.initState();
controller = SuggestionsController(duration);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
void didUpdateWidget(SuggestionsWidget oldWidget) {
super.didUpdateWidget(oldWidget);
controller.dispose();
controller = SuggestionsController(duration);
}
}
从您的示例中尚不清楚query
字符串的来源,但是要完成连接,您需要调用controller.query.add(newQuery)
,StreamBuilder会处理其余部分。
结论
由于您使用的API会产生Futures,因此使用该方法似乎更加简单。缺点是Debouncer类的开销,并添加了Completer将其绑定到FutureBuilder。
流方法很流行,但也包括相当多的开销。如果您不熟悉,正确创建和处理流会很棘手。
答案 1 :(得分:1)
这是其他答案的简单替代方法。
protected function authenticated(Request $request, $user)
{
if ($user->first_time_login) {
$url = '/users/edit';
$user->first_time_login = false;
$user->save();
} else {
$url = $this->redirectTo;
}
return redirect($url);
}
我认为这大致就是您应该做的。
这里有一些使用反跳器来代替流的想法。
import 'package:simple_observable/simple_observable.dart';
final debouncer = Debouncer<String>(Duration(milliseconds: 250));
Future<List<Suggestion>> queryChanged(String query) async {
debouncer.value = query;
return GooglePlaces.getInstance().getAutocompleteSuggestions(await debouncer.nextValue)
}
@override
Widget buildResults(BuildContext context) {
return FutureBuilder<List<Suggestion>>(
future: queryChanged(query),
builder: (BuildContext context, AsyncSnapshot<List<Suggestion>> suggestions) {
if (!suggestions.hasData) {
return Text('No results');
}
return buildLocationSuggestions(suggestions.data);
},
);
}
或使用StreamTransformer。
void queryChanged(query) => debouncer.value = query;
Stream<List<Suggestion>> get suggestions async* {
while (true)
yield GooglePlaces.getInstance().getAutocompleteSuggestions(await debouncer.nexValue);
}
@override
Widget buildResults(BuildContext context) {
return StreamBuilder<List<Suggestion>>(
stream: suggestions,
builder: (BuildContext context, AsyncSnapshot<List<Suggestion>> suggestions) {
if (!suggestions.hasData) {
return Text('No results');
}
return buildLocationSuggestions(suggestions.data);
},
);
}
答案 2 :(得分:0)
我只是这样做,不需要任何库:
void searchWithThrottle(String keyword, {int throttleTime}) {
_timer?.cancel();
if (keyword != previousKeyword && keyword.isNotEmpty) {
previousKeyword = keyword;
_timer = Timer.periodic(Duration(milliseconds: throttleTime ?? 350), (timer) {
print("Going to search with keyword : $keyword");
search(keyword);
_timer.cancel();
});
}
}
答案 3 :(得分:0)
定时器可用于去抖动搜索输入。
Timer debounce;
void handleSearch(String value) {
if (debounce != null) debounce.cancel();
setState(() {
debounce = Timer(Duration(seconds: 2), () {
searchItems(value);
//call api or other search functions here
});
});
}
每当向文本框中添加新输入时,该函数都会取消前一个计时器并开始新的计时器。搜索功能将在不活动 2 秒后启动