Flutter optimize API number of calls in TextField autocomplete implementation - flutter

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?

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?

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

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

Related

Flutter TextFormField call value change method after finish text input

In my screen there are search features as per logic that's working fine but issue is I want to run my search function after stope typing?? So in the TextFormField there are onChanged method, how can we achieve it? I tried lots of ways like comparing DateTime but not able to achieved it.
~ PS : onEditingComplete method works perfect in my case but issues is, To call this method I have to click on return button, without click on return button is it possible?
I would suggest to create a debouncer, something like this.
class Debouncer {
final int milliseconds;
VoidCallback action;
Timer _timer;
Debouncer({this.milliseconds});
run(VoidCallback action) {
if (null != _timer) {
_timer.cancel();
}
_timer = Timer(Duration(milliseconds: milliseconds), action);
}
}
Then initiate the object with your desired time of "after stop typing".
final _debouncer = Debouncer(milliseconds: 1000);
And use it in your onChanged method.
onChanged: (string) {
_debouncer.run(() {
//Perform your search
}
);

How to make a stopwatch that takes a widget off the air?

I need to know how to make this stopwatch, which after 48 hours disables a widget, which in my case is a button. Can someone explain to me how to do it? What classes to use?
I tried to use this, but don't works:
var timer = Timer(Duration(seconds: 1), () => print('done'));
it seems to me that you want this button to be disabled after 2 days of the app that was installed, you need persist the date on the device so that after app-restarts the date will be itself, you need to use a package the persists the data on the device. i recommend shared_preference which is easy to use.
for your case, in the screen where you use the button you need to do this
import 'package:shared_preferences/shared_preferences.dart';
class MyFirstStatefullScreen extends StatefullWidget {
MyFirstStatefullScreenState createState() => MyFirstStatefullScreenState();
}
class MyFirstStatefullScreenState extends State<MyFirstStatefullScreen>{
// some other code that u wrote
bool shouldButtonBeActive = true;
#override
void initState() {
Future.delayed(Duration(0))
.then((_) {
SharedPreferences sharedPrefs= await SharedPreferences.getInstance();
final deadLine = sharedPrefs.getString('deadLine');
if(deadLine == null) {
// this is the first time the app has been installed on the device
// so we need to set the deadLine to N number of days after the installation
final deadLineDate = DateTime.now().add(Duration(days: 2)); // 2 days from the date that the app was installed;
sharedPrefs.setString(deadLineDate.toIso8601String()); // set it so we can check on the successfull state
return;
}
final deadLineDate = DateTime.parse(deadLine); // since we stored it as a string;
/// the deadline is set and is not null
if(DateTime.now().compareTo(deadLineDate) == 0) {
we are successfull, N hours have passed since the intial instalation, now we can disable the button
shouldButtonBeActive = false; // the button should be disabled
}
})
}
Widget build(context) {
// in your UI where you use the button
MaterialButton(
child: Text("Button"),
onPressed: shouldButtonBeActive ? func() : null
)
}
}
PS: we are using the Future inside the initState, because initState dose not allow async ( api calls, storage access) in it.

How to catch speed typing on textField widget?

This way called everytime the input changes. The problem is when user types fast, calling api every time and search result is changing always. (called google map autoComplete api)
How to call api when user stop typing? (Not close keyboard or onSaved)
TextField(
decoration: _inputStyle(),
cursorColor: Palette.defaultColor,
controller: _textEditingController,
onChanged: (value) => setState(() => _autocompletePlace(value)),
)
I would create Debouncer, it would delay the execution of your code by chosen timer.
E.g. user enters '123' - the execution would be delayed by 200ms, user enters '4', last execution is canceled and another delay is created. If user does not enter new data for 200ms, _autocompletePlace("1234") is gonna be executed
import 'package:flutter/foundation.dart';
import 'dart:async';
class Debouncer {
final int milliseconds;
VoidCallback action;
Timer _timer;
Debouncer({ this.milliseconds });
run(VoidCallback action) {
if (_timer != null) {
_timer.cancel();
}
_timer = Timer(Duration(milliseconds: milliseconds), action);
}
}
final _debouncer = Debouncer(milliseconds: 200);
onTextChange(String text) {
_debouncer.run(() => _autocompletePlace(value));
}

Is there a way I can continuously change some text in certain interval of time?

In some area, I want a list of objects to be continuously appeared replacing the previous one in a gap of 2 secs. And in that interval I wanna do some logic.
I tried Flutter.delayed but it doesn't work accordingly in a while loop.
In your initState method, use Timer.periodic(...)
#override
void initState() {
super.initState();
// this code runs after every 2 seconds.
Timer.periodic(Duration(seconds: 2), (timer) {
if (_someCondition) {
timer.cancel(); // if you want to stop this loop use cancel
}
setState(() {
_string = "new value"; // your logic here
});
});
}
Create a timer and put your logic in the function that handles the timer event.
...
...
initstate() {
Timer.periodic( Duration(seconds: 2), (Timer t) {
setState(() => displayTheNextElementOfTheList());
...
//your logic here
});
...
}

How to execute onchanged after a pause in textfield?(Flutter)

Is it possible to execute a event when the text of a TextField is changed but after a pause.
Suppose, I have a search box but I don't want to change the search data after the user enters each letter but instead the search should take place only if the user entered and paused for a while.
Gunter is correct about using a debounce function, but the one in RxDart only works for Observables (as he pointed out you can convert the onChanged events into a stream and go that route). You can also easily implement your own to accept any function.
// Define this function somewhere
import 'dart:async';
// This map will track all your pending function calls
Map<Function, Timer> _timeouts = {};
void debounce(Duration timeout, Function target, [List arguments = const []]) {
if (_timeouts.containsKey(target)) {
_timeouts[target].cancel();
}
Timer timer = Timer(timeout, () {
Function.apply(target, arguments);
});
_timeouts[target] = timer;
}
Then, you can use it like so in your widget
void _onChanged(String val) {
// ...
}
Widget build(BuildContext context) {
// ...
TextField(
// ...
onChanged: (val) => debounce(const Duration(milliseconds: 300), _onChanged, [val]),
)
// ...
}
You probably want something like debounce provided by https://pub.dartlang.org/documentation/rxdart/latest/rx/Observable/debounce.html
new Observable.range(1, 100)
.debounce(new Duration(seconds: 1))
.listen(print); // prints 100