Realtime Searching: Begin search after user is done typing

Issue

In my app I am searching for results as user types something in the a TextField. I’m using Provider where there is a searchProduct() function which is fired every time user types something in the textfield. once the result is fetched I’m calling the notifyListener() function and UI gets updated accordingly.

The problem I’m facing is as the results are being fetched asynchronously, they are not arriving at the same time. Sometimes the last result comes before one of the previous results. This particularly happens when user types in too fast. So with every key stroke this searchProduct() function is being called and it makes network request. This approach is also making too many unnecessary network request, which is not ideal.
What would be the best way to solve this issue so that in a given time when user types a search string the search will begin after user is done typing?

class ProductService extends ChangeNotifier {
  String _searchText;

  String serverUrl = 'https://api.example.com/api';

  String get searchText => _searchText;
  List<Product> products = [];
  bool searching = false;

  void searchProduct(String text) async {
    searching = true;
    notifyListeners();
    _searchText = text;

    var result = await http
        .get("$serverUrl/product/search?name=$_searchText");
    if (_searchText.isEmpty) {
      products = [];
      notifyListeners();
    } else {
      var jsonData = json.decode(result.body);

      List<Map<String, dynamic>> productsJson =
          List.from(jsonData['result'], growable: true);
      if (productsJson.length == 0) {
        products = [];
        notifyListeners();
      } else {
        products = productsJson
            .map((Map<String, dynamic> p) => Product.fromJson(p))
            .toList();
      }
      searching = false;
      notifyListeners();
    }
  }
}

Solution

User RestartableTimer and set duration of count down lets say to 2 seconds. First time the user types a character the timer will initialize and then every time a character is typed it will reset the timer. If the user stops typing for 2 seconds the callback which contains the network request will fire. Obviously the code needs improvement to account for other scenarios for example if the request should be cancelled before it triggers for what ever reason.

TextField(
      controller: TextEditingController(),
      onChanged: _lookupSomething,
);


RestartableTimer timer;
static const timeout = const Duration(seconds: 2);

_lookupSomething(String newQuery) {

  // Every time a new query is passed as the user types in characters
  // the new query might not be known to the callback in the timer 
  // because of closures. The callback might consider the first query that was
  // passed during initialization. 
  // To be honest I don't know either if referring to tempQuery this way
  // will fix the issue.  

  String tempQuery = newQuery;

  if(timer == null){
    timer = RestartableTimer(timeout, (){
      myModel.search(tempQuery);
    });
  }else{
    timer.reset();
  }
}

Answered By – unbalanced_equation

Answer Checked By – David Marino (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *