Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to debounce search suggestions in flutter's SearchPage Widget?

Tags:

flutter

dart

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

To summarize what I've done so far:

1) In my widget I call

showSearch(context: context, delegate: _delegate);

2) My delegate looks like this:

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

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)?

**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.

like image 542
Shehabic Avatar asked Oct 21 '18 11:10

Shehabic


2 Answers

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.

Stream<List<Suggestion>> get suggestions => 
  debouncer.values.transform(StreamTransformer.fromHandlers(
    handleData: (value, sink) => sink.add(GooglePlaces.getInstance()
      .getAutocompleteSuggestions(value))));
like image 159
Jacob Phillips Avatar answered Sep 27 '22 07:09

Jacob Phillips


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();
      });
    }
  }
like image 27
Oussaki Avatar answered Sep 27 '22 07:09

Oussaki