我需要让Google使用默认的FLUTER的SearchPage来放置搜索建议,每当用户开始键入时,我都需要给出自动完成的建议,而我使用FutureBuilder异步地实现了这一点,现在的问题是,我需要在500ms或更长时间内取消搜索请求的分派,而不是在用户仍在键入时浪费大量请求

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

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毫秒过go

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

**我知道有一些像TypeAhead这样的第三方自动完成小工具正在(从头开始)这样做,但我现在不想使用这个.

推荐答案

Update:我为此制作了一个用于回调、期货和/或流的包.https://pub.dartlang.org/packages/debounce_throttle美元.使用它可以简化下面描述的两种方法,特别是基于流的方法,因为不需要引入新的类.这里有一个dart https://dartpad.dartlang.org/e4e9c07dc320ec400a59827fff66bb49的例子.

至少有两种方法可以做到这一点,基于Future的方法和基于Stream的方法.由于内置了debounce 功能,类似的问题也被指向使用流,但让我们看看这两种方法.

Future-based approach

Future本身是不可取消的,但它们使用的基本Timer是可取消的.下面是一个简单的类,它实现了基本的go Bounce功能,使用回调而不是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());
        }
      },
    );
  }
}

Stream-based approach

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

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

要在Ffltter中使用这个控制器类,让我们创建一个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处理睡觉.

Conclusion

由于您正在使用的API产生期货yield ,因此使用该方法似乎更简单一些.缺点是Debouler类和添加Completer以将其绑定到FutureBuilder的开销.

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

Flutter相关问答推荐

如何在容器中移动图标按钮?

为什么当我使用Riverpod更新状态时,列表会被刷新?

如何在不注销的情况下刷新Firebase身份验证用户

SingleChildScrollView在ErrorWidget.builder中不工作

任务';:app:check DebugDuplicateClasss';的Ffltter Share_plus抛出执行失败

Flutter 数独网格

BaseInputfield和Textfield有什么不同?

火焰Flutter 在多个向量之间插补2

如何在 Dart/Flutter 中克隆 Future

Flutter:如何在 Future 函数上添加网络判断和 showDialog

我在手机上找不到我的 Flutter 应用缓存数据

Flutter RIverpod 奇怪地改变状态

java.lang.IncompatibleClassChangeError:找到接口 com.google.android.gms.location.SettingsClient,

如何将 chrome web 模拟器添加到 vs code

statefulWidget 无法进行所需更改的问题

如何根据字符串值动态构建和显示小部件?

Flutter 如何使用 ClipPath 编辑容器的顶部?

如何在屏幕按钮中排列 row() (我想在按钮中放置屏幕导航图标)

构建失败 - 无法解析 io.grpc:grpc-core:[1.28.0]. (是的,我已经升级到 mavenCentral())

如何将变量翻译成其他语言