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
}
);
Related
I have a requirement that when my flutter_slidable is open, tapping on it closes it, rather than executing my onTap callback.
I'm not seeing an isOpen field to check whether the slidable is open. I have a workaround where I check to see how long it took to close after an onTap. If it closes fast, I assume it was already closed.
Is there a "real" way? Here's my workaround
const int closingTimeMS = 500;
final stopwatch = Stopwatch();
stopwatch.start();
await slidable.close(duration: const Duration(milliseconds: closingTimeMS));
if (stopwatch.elapsedMilliseconds < closingTimeMS / 2) {
myOnTap();
}
Calling this in an onTap/onPressed solved it for me
final isClosed = Slidable.of(context)!.actionPaneType.value == ActionPaneType.none;
if (isClosed) {
Slidable.of(context)!.openStartActionPane();
} else {
Slidable.of(context)!.close();
}
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.
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(); }
}
I'm currently trying Provider as a state management solution, and I understand that it can't be used inside the initState function.
All examples that I've seen call a method inside a derived ChangeNotifier class upon user action (user clicks a button, for example), but what if I need to call a method when initialising my state?
Motivation:
Creating a screen which loads assets (async) and shows progress
An example for the ChangeNotifier class (can't call add from initState):
import 'package:flutter/foundation.dart';
class ProgressData extends ChangeNotifier {
double _progress = 0;
double get progress => _progress;
void add(double dProgress) {
_progress += dProgress;
notifyListeners();
}
}
You can call such methods from the constructor of your ChangeNotifier:
class MyNotifier with ChangeNotifier {
MyNotifier() {
someMethod();
}
void someMethod() {
// TODO: do something
}
}
Change your code to this
class ProgressData extends ChangeNotifier {
double _progress = 0;
double get progress => _progress;
void add(double dProgress) async {
// Loading Assets maybe async process with its network call, etc.
_progress += dProgress;
notifyListeners();
}
ProgressData() {
add();
}
}
In initState all the of(context) things don't work correctly, because the widget is not fully wired up with every thing in initState.
You can use this code:
Provider.of<ProgressData>(context, listen: false).add(progress)
Or this code:
Future.delayed(Duration.zero).then(_){
Provider.of<ProgressData>(context).add(progress)
}):
So an AssetLoader class which reports on its progress will look something like this, I guess:
import 'package:flutter/foundation.dart';
class ProgressData extends ChangeNotifier {
double _progress = 0;
ProgressData() {
_loadFake();
}
Future<void> _loadFake() async {
await _delayed(true, Duration(seconds: 1));
_add(1.0);
await _delayed(true, Duration(seconds: 2));
_add(2.0);
await _delayed(true, Duration(seconds: 3));
_add(3.0);
}
// progress
double get progress => _progress;
// add
void _add(double dProgress) {
_progress += dProgress;
notifyListeners();
}
// _delayed
Future<dynamic> _delayed(dynamic returnVal, Duration duration) {
return Future.delayed(duration, () => returnVal);
}
}
As Fateme said:
the widget is not fully wired up with everything in initState
Also, you can use something like this in your initState
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
Provider.of<ProgressData>(context, listen: false).add(5);
});
I think it's more standard!
Be aware that you should use the correct context! I mean the context of the Builder!
The problem here lies with the fact that context does not exist yet in initState as extensively explained by the other answers. It doesn't exist because it hasn't yet been made a part of the widget tree.
Calling a method
If you're not assigning any state and only calling a method then initState would be the best place to get this done.
// The key here is the listen: false
Provider.of<MyProvider>(context, listen: false).mymethod();
The code above is allowed by Flutter because it doesn't have to listen for anything. In short, it's a one off. Use it where you only want to do something instead of read/listen to something.
Listening to changes
Alternatively, if you need to listen to changes from Provider then the use of didChangeDependencies would be the best place to do so as context would exist here as in the docs.
This method is also called immediately after initState.
int? myState;
#override
void didChangeDependencies() {
// No listen: false
myState = Provider.of<MyProvider>(context).data;
super.didChangeDependencies();
}
If you've never used didChangeDependencies before, what it does is get called whenever updateShouldNotify() returns true. This in turn lets any widgets that requested an inherited widget in build() respond as needed.
I'd usually use this method in a FutureBuilder to prevent reloading data when data already exists in Provider after switching screens. This way I can just check Provider for myState and skip the preloader (if any) entirely.
Hope this helps.
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