5

I am trying to implement autocomplete for selecting places in my app. For that I use Here Maps API. At the moment I have this for a TextField:

onChanged: (query){
    print("Current value is: ${query}");
    if(query) { getPlacesFromHereMaps(query); } 
}, 

Here, each time user enters some letter Here autocomplete API is being called.

So, if user types "New York" that means app will call API for about 8 times which I find too much. Is there a way to optimize this?

CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
harunB10
  • 4,823
  • 15
  • 63
  • 107
  • 4
    a good solution is to use what's called a `debounce`. Basically, this waits for the user to stop typing for a certain amount of time before calling the api. please refer to [this link](https://stackoverflow.com/questions/51791501/how-to-debounce-textfield-onchange-in-dart) for the implementation. – Karim Elghamry Sep 01 '19 at 14:38

3 Answers3

4

You can call the API whenever the user finshes typing a word or after every 3 (or 2) characters.

But don't forget to call the API when the user submits the query(using onSubmitted).

Solution Code:

onChanged: (query){
    print("Current value is: ${query}");
    if((query.length%3==0)||(query[query.length-1]==' ')) { getPlacesFromHereMaps(query); } 

onSubmitted: (query){
   getPlacesFromHereMaps(query);
 }
}, 

=========

Alternate Solution:

As per @Karim Elghamry 's advice and @CopsOnRoad 's concern you can even use debounce to improve your UX.

In your widget state declare a controller and timer:

final _searchQuery = new TextEditingController();
Timer _debounce;

Add a listener method:

_onSearchChanged() {
    if (_debounce?.isActive ?? false) _debounce.cancel();
    _debounce = Timer(const Duration(milliseconds: 500), () {
        getPlacesFromHereMaps(query);
    });
}

Hook and un-hook the method to the controller:

@override
void initState() {
    super.initState();
    _searchQuery.addListener(_onSearchChanged);
}

@override
void dispose() {
    _searchQuery.removeListener(_onSearchChanged);
    _searchQuery.dispose();
    super.dispose();
}

In your build tree bind the controller to the TextField:

child: TextField(
        controller: _searchQuery,
        [...]
    )

Source: How to debounce Textfield onChange in Dart?

Mohit Shetty
  • 1,551
  • 8
  • 26
0

onChanged is doing its job. According to docs:

The text field calls the onChanged callback whenever the user changes the text in the field. If the user indicates that they are done typing in the field (e.g., by pressing a button on the soft keyboard), the text field calls the onSubmitted callback.

If you want to optimize, you can do something like:

onChanged(query) {
  if (query.length < 2) return;
  // if the length of the word is less than 2, stop executing your API call.

  // rest of your code
  getPlacesFromHereMaps(query);

}
CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
  • This not solves the problem, I found that using "Timer _debounce" works like a charm. – Marco Dec 04 '20 at 13:49
  • @CopsOnRoad how do you think it's solve OPs question ? just curious to know, your solution will call API almost everytime a user enters a character(except length <2) ? – Rishabh Agrawal Nov 25 '21 at 11:17
0

Considering the popular node package 'debounce', below is the simple implementation

/// Implementation of well known 'Debounce' node package in Dart
class Debounce {
  final Function _function;
  final Duration _duration;
  Timer _timer;
  int _lastCompletionTime;

  Debounce(this._duration, this._function)
      : assert(_duration != null, "Duration can not be null"),
        assert(function != null, "Function can not be null");

  void schedule() {
    var now = DateTime.now().millisecondsSinceEpoch;

    if (_timer == null || (_timer != null && !_timer.isActive)) {
      _lastCompletionTime = now + _duration.inMilliseconds;
      _timer = Timer(_duration, _function);
    } else {
      _timer?.cancel(); // doesn't throw exception if _timer is not active
      int wait = _lastCompletionTime - now; // this uses last wait time, so we need to wait only for calculated wait time
      _lastCompletionTime = now + wait;
      _timer = Timer(Duration(milliseconds: wait), _function);
    }
  }
}

Usage:

1. Define

var debounce = Debounce(Duration(seconds: 1), () {
  getPlacesFromHereMaps(query);
});

2. Call every time when value changes

onChanged: (query){
    print("Current value is: ${query}");
    if(query.trim().length > 3) { d.schedule(); }
}
Mayur Prajapati
  • 597
  • 1
  • 6
  • 14