Dart 如何在颤振';s搜索页面小部件?

Dart 如何在颤振';s搜索页面小部件?,dart,flutter,Dart,Flutter,我需要Google Places使用默认的Flatter的搜索页面提供搜索建议,每当用户开始键入时,我需要提供自动完成建议,我使用FutureBuilder异步实现这一点,现在的问题是,我需要在500毫秒或更长时间内取消发送搜索请求,而不是在用户仍在键入时浪费大量请求 总结一下我到目前为止所做的工作: 1) 在我的小部件中,我调用 showSearch(context: context, delegate: _delegate); 2) 我的代表如下所示: class _LocationSea

我需要Google Places使用默认的Flatter的搜索页面提供搜索建议,每当用户开始键入时,我需要提供自动完成建议,我使用
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,
    );
  }
}
类_LocationSearchDelegate扩展了SearchDelegate{
@凌驾
列出buildActions(BuildContext上下文){
返回[
图标按钮(
工具提示:“清除”,
图标:常量图标(图标清除),
已按下:(){
查询=“”;
展示建议(背景);
},
)
];
}
@凌驾
小部件buildLeading(BuildContext上下文)=>IconButton(
工具提示:“返回”,
图标:动画指令(
图标:animateDictions.menu\u箭头,
进展:过渡化,
),
已按下:(){
关闭(上下文,空);
},
);
@凌驾
小部件构建结果(构建上下文){
回归未来建设者(
未来:GooglePlaces.getInstance().getAutocompleteSuggestions(查询),
生成器:(BuildContext上下文,异步快照建议){
如果(!suggestions.hasData){
返回文本(“无结果”);
}
返回buildLocationSuggestions(suggestions.data);
},
);
}
@凌驾
小部件构建建议(构建上下文){
返回构建结果(上下文);
}
Widget buildLocationSuggestions(列表建议){
返回ListView.builder(
itemBuilder:(上下文,索引)=>ListTile(
前导:图标(图标位置打开),
标题:文本(建议[索引]),
onTap:(){
展示结果(背景);
},
),
itemCount:suggestions.length,
);
}
}
3-我需要节流/去盎司搜索,直到xxx毫秒过去

我有一个想法,是否有一个简单的方法将FutureBuilder转换为Stream并使用Stream builder(我在一些文章中读到它支持去Bouncing)


**我知道有一些第三方自动完成小部件,如
TypeAhead
,正在这样做(从头开始),但我现在不想使用它。

更新:我为此制作了一个包,可用于回调、期货和/或流。使用它将简化下面描述的两种方法,尤其是基于流的方法,因为不需要引入新类。下面是一个dartpad示例

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

基于未来的方法

Future
s本身是不可取消的,但它们使用的底层
Timer
s是可取消的。下面是一个简单的类,它实现了基本的去盎司功能,使用回调而不是未来

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));
  }  
}
基于流的方法

由于所讨论的数据来自未来而不是流,因此我们必须设置一个类来处理查询输入和建议输出。幸运的是,它可以自然地处理输入流的去抖动

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();
  }
}
从您的示例中不清楚
query
字符串来自何处,但要完成连接,您需要调用
controller.query.add(newQuery)
,然后StreamBuilder处理其余部分

结论

由于您使用的API会产生期货收益,因此使用这种方法似乎更简单一些。缺点是Debouncer类的开销,并添加了一个补足符将其绑定到FutureBuilder


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

这里有一个简单的替代答案

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);
      },
    );
  }
import'包:去盎司节流/去盎司节流.dart';
最终去抖动器=去抖动器(持续时间(毫秒:250));
未来查询更改(字符串查询)异步{
debouncer.value=查询;
返回GooglePlaces.getInstance().getAutocompleteSuggestions(等待debouncer.nextValue)
}
@凌驾
小部件构建结果(构建上下文){
回归未来建设者(
未来:查询更改(查询),
生成器:(BuildContext上下文,异步快照建议){
如果(!suggestions.hasData){
返回文本(“无结果”);
}
返回buildLocationSuggestions(suggestions.data);
},
);
}
我想这大概就是你应该做的

这里有几个使用流的想法,使用去Bouncer

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);
      },
    );
  }
void queryChanged(query)=>debouncer.value=query;
流获取异步建议*{
while(true)
生成GooglePlaces.getInstance().getAutocompleteSuggestions(wait debouncer.nexValue);
}
@凌驾
小部件构建结果(构建上下文){
返回流生成器(
流:建议,
生成器:(BuildContext上下文,异步快照建议){
如果(!suggestions.hasData){
返回文本(“无结果”);
}
返回buildLocationSuggestions(suggestions.data);
},
);
}
或者使用流变压器

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

我就是这样做的,不需要库:

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

定时器可用于消除搜索输入的抖动

  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
      });
    });
  }
每当向文本b添加新输入时
Stream<List<Suggestion>> get suggestions => 
  debouncer.values.transform(StreamTransformer.fromHandlers(
    handleData: (value, sink) => sink.add(GooglePlaces.getInstance()
      .getAutocompleteSuggestions(value))));
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();
      });
    }
  }
  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
      });
    });
  }