gpt4 book ai didi

How to debounce search suggestions in flutter's SearchPage Widget?(如何在Fighter的SearchPage Widget中去掉搜索建议?)

转载 作者:bug小助手 更新时间:2023-10-25 18:27:21 30 4
gpt4 key购买 nike



I need to have Google Places search suggestions using the default flutter's SearchPage, whenever the user starts typing I need to give autocomplete suggestions and I achieve this Asynchronously using FutureBuilder, the problem now is that I need to debounce the dispatch of search requests for 500ms or more rather than wasting a lot of requests while the user is still typing

我需要让谷歌放置搜索建议使用默认扑翼的SearchPage,每当用户开始输入我需要给出自动补全建议,我使用FutureBuilder异步实现了这一点,现在的问题是,我需要取消分派的搜索请求500ms或更长时间,而不是浪费大量的请求,而用户仍在输入



To summarize what I've done so far:

总结一下我到目前为止所做的工作:



1) In my widget I call

1)在我的小部件中调用



showSearch(context: context, delegate: _delegate);


2) My delegate looks like this:

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-I need to throttle / debounce searching until xxx Milliseconds have passed

3-我需要限制/取消搜索,直到xxx毫秒过去



I have 1 idea in mind, would there be an easy way to convert FutureBuilder to Stream and Use Stream builder, (which I read in some articles that it supports debouncing)?

我有一个想法,有没有一种简单的方法可以将FutureBuilder转换为Stream并使用Stream Builder(我在一些文章中读到了它支持去抖动)?



**I am aware that there are some 3rd party AutoComplete widgets like TypeAhead that's doing that (from scratch) but I don't wanna use this at the moment.

**我知道有一些第三方自动完成小工具,比如Typehead,就是(从头开始)这样做的,但我现在不想用这个。


更多回答

Have you solved your problem? I want to achieve the same above functionality but is not able to do ..

你的问题解决了吗?我想实现相同的上述功能,但无法实现。

优秀答案推荐

Update: I made a package for this that works with callbacks, futures, and/or streams. https://pub.dartlang.org/packages/debounce_throttle. Using it would simplify both of the approaches described below, especially the stream based approach as no new classes would need to be introduced. Here's a dartpad example https://dartpad.dartlang.org/e4e9c07dc320ec400a59827fff66bb49.

更新:我为此制作了一个与回调、期货和/或流一起工作的包。Https://pub.dartlang.org/packages/debounce_throttle.使用它将简化下面描述的两种方法,特别是基于流的方法,因为不需要引入新的类。下面是一个飞镖示例https://dartpad.dartlang.org/e4e9c07dc320ec400a59827fff66bb49.



There are at least two ways of doing this, a Future based approach, and a Stream based approach. Similar questions have gotten pointed towards using Streams since debouncing is built in, but let's look at both methods.

至少有两种方法可以做到这一点,一种是基于未来的方法,另一种是基于流的方法。类似的问题已经指向使用流,因为去抖动是内置的,但让我们看看这两种方法。



Future-based approach

面向未来的方法



Futures themselves aren't cancelable, but the underlying Timers they use are. Here's a simple class that implements basic debounce functionality, using a callback instead of a Future.

期货本身是不可取消的,但它们使用的基础计时器是可以取消的。下面是一个简单的类,它使用回调而不是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));
}
}


Then to use it (DartPad compatible):

然后使用它(与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());
}


Now to turn the callback into a Future, use a Completer. Here's an example that debounces the List<Suggestion> call to Google's API.

现在,要将回调变成未来,请使用补充器。下面的示例揭穿了对Google API的list 调用。



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());
}
},
);
}
}


Stream-based approach

基于流的方法



Since the data in question arrives from a Future and not a Stream, we have to setup a class to handle query inputs and suggestion outputs. Luckily it handles debouncing the input stream naturally.

由于所讨论的数据来自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();
}
}


To use this controller class in Flutter, let's create a StatefulWidget that will manage the controller's state. This part includes the call to your function buildLocationSuggestions().

为了在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);
}
}


It's not clear from your example where the query string comes from, but to finish wiring this up, you would call controller.query.add(newQuery) and StreamBuilder handles the rest.

从您的示例中不清楚查询字符串来自哪里,但要完成连接,您将调用Controler.query.add(NewQuery),StreamBuilder将处理其余部分。



Conclusion

结论



Since the API you're using yields Futures, it seems a little more straightforward to use that approach. The downside is the overhead of the Debouncer class and adding a Completer to tie it in to FutureBuilder.

由于您正在使用的API会产生期货收益,因此使用这种方法似乎更直接一些。缺点是Debouler类和添加Completer以将其绑定到FutureBuilder的开销。



The stream approach is popular, but includes a fair amount of overhead as well. Creating and disposing of the streams correctly can be tricky if you're not familiar.

流方法很流行,但也包括相当数量的开销。如果您不熟悉,那么正确地创建和处理流可能会很棘手。



I Simply did it this way no library required:

我只是这样做,不需要库:


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();
});
}
}


Here's a simple alternative to the other answer.

这是另一个答案的简单替代方案。



import 'package:debounce_throttle/debounce_throttle.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);
},
);
}


That's roughly how you should be doing it I think.

我认为这大概就是你应该做的事情。



Here are a couple of ideas for using a stream instead, using the debouncer.

这里有几个想法,而不是使用流,使用去抖动。



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);
},
);
}


Or with a StreamTransformer.

或者使用StreamTransformer。



Stream<List<Suggestion>> get suggestions => 
debouncer.values.transform(StreamTransformer.fromHandlers(
handleData: (value, sink) => sink.add(GooglePlaces.getInstance()
.getAutocompleteSuggestions(value))));


I had trouble getting @Jacob Phillip's debounce_throttle package to work, as the code that worked for him back in 2018 no longer seems to work with the latest versions of Flutter / Dart. It waits for the debounce time to be reached but then executes all of the attempts at once after instead of just the last one.

我很难让@Jacob Phillip的DEBEAKE_THROTTLE程序包工作,因为早在2018年为他工作的代码似乎不再适用于最新版本的Ffltter/DART。它等待达到去抖动时间,但随后立即执行所有尝试,而不是仅执行最后一次尝试。


I was able to get it working with some modifications. I would have posted this as a comment on his answer, but it's too long.

经过一些修改,我能够让它工作起来。我本想把这篇文章作为对他的回答的评论,但它太长了。


final _debouncer = Debouncer<String>(const Duration(milliseconds: 500), initialValue: '');
late String userQuery;

Future<Iterable<LocationModel>> onSearchChanged(TextEditingValue textEditingValue) async {
// Debounce for half a second, so we don't make unnecessary api calls as the user types.
_debouncer.value = userQuery = textEditingValue.text;
await _debouncer.nextValue;
if (textEditingValue.text != userQuery) {
return const Iterable<LocationModel>.empty();
}
// Only retrieve location suggestions if the user typed at least 4 characters...
if (textEditingValue.text.length < _userQueryMinSuggestionsLength) {
return const Iterable<LocationModel>.empty();
}

// Get user's location.
var position = await _locationService.getPosition();
// Get location suggestions.
var propertyMode = await _userSettingsService.propertyMode;
try {
_locationSuggestions = await _placesService.getLocationSuggestionsAsync(
propertyMode, textEditingValue.text, position.latitude, position.longitude, LocationSearchType.property);
} catch (e) {
handleError(e, message: 'Error retrieving location suggestions');
}
notifyListeners();
return _locationSuggestions ?? [];
}


Timer can be used to debounce search input.

定时器可用于取消搜索输入。


  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
});
});
}

whenever a new input is added to the text box the function cancels previous timer and start a new one. The search function will be only initiated after 2 seconds of inactivity

每当向文本框添加新输入时,该函数都会取消先前的计时器并启动新的计时器。搜索功能仅在处于非活动状态2秒后启动



Adding the updated code for null safety in case anyone is looking for just copy paste. I know programmer can scroll two pages instead of just appending a '?' after the variables :)

为零安全添加了更新的代码,以防任何人只是在寻找复制粘贴。我知道程序员可以滚动两个页面,而不是只附加一个‘?’在变量后面:)


class Debouncer<T> {
Debouncer({required this.duration, required 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));
}
}

更多回答

Thanks @jacob once I am back from vacation I will give your solution a try and marker it correct if it works, generally it sounds right, now speaking about "query" it's a property set on the delegate from the flutter team who created the SearchPage widget, it reoresents the text that's currently in the TextField, which I consider as a bad design, instead of explicitly sending it along with methods that are invoked to get suggestions, they just decided to keep it on a class level, there are indeed many other ways than doing it this way.

谢谢@Jacob一旦我度假回来,我会试一试你的解决方案,如果它有效的话,标记它是正确的,通常听起来是正确的,现在说到“Query”,它是创建SearchPage小部件的Ffltter团队的代表上设置的一个属性,它表示当前在Textfield中的文本,我认为这是一个糟糕的设计,而不是显式地将其与调用来获取建议的方法一起发送,他们只是决定将其保持在类级别,除了这种方式,确实还有许多其他方法。

Yeah I kinda overdid this one while avoiding other work. It really bugged me the best answer I found was to convert it to a stream. Anyway, cheers!

是的,我在逃避其他工作的同时,做得有点过头了。这真的让我很烦恼,我找到的最好的答案就是把它转换成一条流。不管怎样,干杯!

Thanks again @Jacob Phillips, Please update the answer to indicate that I have to recreate a completer every time the method completes, otherwise It throws an exception, even though I use Completer in Rx, I didn't associate it to that right away, it took me a while to understand what's going on.

再次感谢@Jacob Phillips,请更新答案以指示我必须在每次方法完成时重新创建完成器,否则它会抛出异常,即使我在Rx中使用Completer,我也没有立即将其与之关联,我花了一段时间才理解发生了什么。

Okay will do tomorrow. The package helps mitigate that issue since it recreates the completer internally. Its link shows an example, I just havent changed the answer.

好的,明天就可以了。该包有助于缓解该问题,因为它在内部重新创建了完成器。它的链接显示了一个例子,我只是没有改变答案。

@Luca Just use the Debouncer class from simple_observable

@Luca只需使用SIMPLE_EATABLABLE中的Debouler类

But how to integrate this code with Widget buildResults(BuildContext context) or Widget buildSuggestions(BuildContext context)

但是如何将这些代码与Widget BuildResults(BuildContext Context)或Widget BuildSuggestions(BuildContext Context)集成呢?

This answer seems to be a bit outdated. Your simple_observable package no longer has a Debouncer class

这个答案似乎有点过时了。您的Simple_Observable包不再有Debouler类

yeah i moved it to a package named debounce_throttle

是的,我把它移到了一个名为“去弹跳_节流”的包裹里。

i tried to use your Future code but it still executes it more than once. shouldnt this be executed only once? e.g. 2 seconds. i type fast 3 numbers , it doesnt execute right away, only after 2 seconds have elapsed but still runs the function 3x.

我试图使用您的Future代码,但它仍然多次执行它。这不是应该只执行一次吗?例如2秒。我输入了快速的3个数字,它没有立即执行,只在2秒后执行,但仍然运行函数3x。

@JacobPhillips yes chitgoks is right it is getting called 3x

@JacobPhillips是的,Chitgoks是对的,它被称为3x

This code not working as I expected, because it's still call a request multiple times based on total user character typing

这段代码没有像我预期的那样工作,因为它仍然根据用户的全部字符输入多次调用请求

But how to integrate this code with Widget buildResults(BuildContext context) or Widget buildSuggestions(BuildContext context)

但是如何将这些代码与Widget BuildResults(BuildContext Context)或Widget BuildSuggestions(BuildContext Context)集成呢?

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com