How to catch speed typing on textField widget? - flutter

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

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

Flutter GetX forms validation

I am looking for an example of how to handle forms and validation in best practice with GetX?
Is there any good example of that or can someone show me an example of how we best can do this?
Here's an example of how you could use GetX's observables to dynamically update form fields & submit button.
I make no claim that this is a best practice. I'm sure there's better ways of accomplishing the same. But it's fun to play around with how GetX can be used to perform validation.
Form + Obx
Two widgets of interest that rebuild based on Observable value changes:
TextFormField
InputDecoration's errorText changes & will rebuild this widget
onChanged: fx.usernameChanged doesn't cause rebuilds. This calls a function in the controller usernameChanged(String val) when form field input changes.
It just updates the username observable with a new value.
Could be written as:
onChanged: (val) => fx.username.value = val
ElevatedButton (a "Submit" button)
onPressed function can change between null and a function
null disables the button (only way to do so in Flutter)
a function here will enable the button
class FormObxPage extends StatelessWidget {
const FormObxPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
FormX fx = Get.put(FormX()); // controller
return Scaffold(
appBar: AppBar(
title: const Text('Form Validation'),
),
body: SafeArea(
child: Container(
alignment: Alignment.center,
margin: const EdgeInsets.symmetric(horizontal: 5),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Obx(
() {
print('rebuild TextFormField ${fx.errorText.value}');
return TextFormField(
onChanged: fx.usernameChanged, // controller func
decoration: InputDecoration(
labelText: 'Username',
errorText: fx.errorText.value // obs
)
);
},
),
Obx(
() => ElevatedButton(
child: const Text('Submit'),
onPressed: fx.submitFunc.value, // obs
),
)
],
),
),
),
);
}
}
GetX Controller
Explanation / breakdown below
class FormX extends GetxController {
RxString username = RxString('');
RxnString errorText = RxnString(null);
Rxn<Function()> submitFunc = Rxn<Function()>(null);
#override
void onInit() {
super.onInit();
debounce<String>(username, validations, time: const Duration(milliseconds: 500));
}
void validations(String val) async {
errorText.value = null; // reset validation errors to nothing
submitFunc.value = null; // disable submit while validating
if (val.isNotEmpty) {
if (lengthOK(val) && await available(val)) {
print('All validations passed, enable submit btn...');
submitFunc.value = submitFunction();
errorText.value = null;
}
}
}
bool lengthOK(String val, {int minLen = 5}) {
if (val.length < minLen) {
errorText.value = 'min. 5 chars';
return false;
}
return true;
}
Future<bool> available(String val) async {
print('Query availability of: $val');
await Future.delayed(
const Duration(seconds: 1),
() => print('Available query returned')
);
if (val == "Sylvester") {
errorText.value = 'Name Taken';
return false;
}
return true;
}
void usernameChanged(String val) {
username.value = val;
}
Future<bool> Function() submitFunction() {
return () async {
print('Make database call to create ${username.value} account');
await Future.delayed(const Duration(seconds: 1), () => print('User account created'));
return true;
};
}
}
Observables
Starting with the three observables...
RxString username = RxString('');
RxnString errorText = RxnString(null);
Rxn<Function()> submitFunc = Rxn<Function()>(null);
username will hold whatever was last input into the TextFormField.
errorText is instantiated with null initial value so the username field is not "invalid" to begin with. If not null (even empty string), TextFormField will be rendered red to signify invalid input. When a non-valid input is in the field, we'll show an error message. (min. 5 chars in example:)
submitFunc is an observable for holding a submit button function or null, since functions in Dart are actually objects, this is fine. The null value initial assignment will disable the button.
onInit
The debounce worker calls the validations function 500ms after changes to the username observable end.
validations will receive username.value as its argument.
More on workers.
Validations
Inside validations function we put any types of validation we want to run: minimum length, bad characters, name already taken, names we personally dislike due to childhood bullies, etc.
For added realism, the available() function is async. Commonly this would query a database to check username availability so in this example, there's a fake 1 second delay before returning this validation check.
submitFunction() returns a function which will replace the null value in submitFunc observable when we're satisfied the form has valid inputs and we allow the user to proceed.
A little more realistic, we'd prob. expect some return value from the submit button function, so we could have the button function return a future bool:
Future<bool> Function() submitFunction() {
return () async {
print('Make database call to create ${username.value} account');
await Future.delayed(Duration(seconds: 1), () => print('User account created'));
return true;
};
}
GetX is not the solution for everything but it has some few utility methods which can help you achieve what you want. For example you can use a validator along with SnackBar for final check. Here is a code snippet that might help you understand the basics.
TextFormField(
controller: emailController,
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: (value) {
if (!GetUtils.isEmail(value))
return "Email is not valid";
else
return null;
},
),
GetUtils has few handy methods for quick validations and you will have to explore each method to see if it fits your need.

Flutter optimize API number of calls in TextField autocomplete implementation

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

How to detect user has stopped typing in TextField?

I am using a TextField under which onChanged function
I am calling my code but the issue right now is the code gets execute everytime a new word is either entered or deleted.
what I am looking for is how to recognize when a user has stopped typing.
means adding some delay or something like that.
I have tried adding delay also using Future.delayed function but that function also gets executed n number of times.
TextField(
controller: textController,
onChanged: (val) {
if (textController.text.length > 3) {
Future.delayed(Duration(milliseconds: 450), () {
//code here
});
}
setState(() {});
},
)
Thanks to #pskink
I was able to achieve the functionality I was looking for.
import stream_transform package in your pubspec.yaml
stream_transform: ^0.0.19
import 'package:stream_transform/stream_transform.dart';
StreamController<String> streamController = StreamController();
#override
void initState() {
streamController.stream
.transform(debounce(Duration(milliseconds: 400)))
.listen((s) => _validateValues());
super.initState();
}
//function I am using to perform some logic
_validateValues() {
if (textController.text.length > 3) {
// code here
}else{
// some other code here
}
}
TextField code
TextField(
controller: textController,
onChanged: (val) {
streamController.add(val);
},
)
In my case I also required flutter async.
//pubspec.yaml
stream_transform: ^2.0.0
import 'dart:async';
import 'package:stream_transform/stream_transform.dart';
StreamController<String> streamController = StreamController();
// In init function
streamController.stream
.debounce(Duration(seconds: 1))
.listen((s) => {
// your code
});
// In build function
TextField(
style: TextStyle(fontSize: 16),
controller: messageController,
onChanged: (val) {
myMetaRef.child("isTyping").set(true);
streamController.add(val);
},
)
I find something so lighter in this link
it define this class
class Debouncer {
final int milliseconds;
Timer? _timer;
Debouncer({this.milliseconds=500});
run(VoidCallback action) {
if (null != _timer) {
_timer!.cancel();
}
_timer = Timer(Duration(milliseconds: milliseconds), action);
}
}
this sample will help you more
TextField(
decoration: new InputDecoration(hintText: 'Search'),
onChanged: (string) {
_debouncer.run(() {
print(string);
//perform search 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