How do i pause and resume quiver.async CountdownTimer in flutter - flutter

i am trying to implement Countdown Timer in flutter. I got it working but cannot implement Pause and Resume feature of the class. Below is what i have tried:
import 'package:flutter/material.dart';
import 'package:quiver/async.dart';
void startTimer() {
CountdownTimer countDownTimer = new CountdownTimer(
new Duration(seconds: _start),
new Duration(seconds: 2),
);
var sub = countDownTimer.listen(null);
sub.onData((duration) {
// Here i tried try to check a bool variable and pause the timer
if(pauseTimer == true){
sub.pause();
}
// Here i tried try to check a bool variable and resume the timer
else if(pauseTimer == false){
sub.resume();
}
setState(() {
_current = _start - duration.elapsed.inSeconds;
});
if(_current == 0){
//Do something here..
}
});
sub.onDone(() {
print("Done");
sub.cancel();
});
}
The problem however is that only Pause is working, while the Resume is not working. Please any idea how to get both Pause and Resume working from button click.
Thanks

sub.resume() is not working because after pausing the timer onData does not trigger. You can simply make a single FlatButton and implement onTap just like this.
StreamSubscription sub; // declare it as a class variable and then assign it in function
bool isPaused =false; /// your state class variable
onTap :(){
if(isPaused)
sub?.resume(); // performing a null check operation here...
else
sub?.pause(); // ...if sub is null then it won't call any functions
}
//// Note:- make sure to cancel sub like
sub?.cancel();

Related

Timer does not stop

Problem
I'm doing a chat. I want the program to try to get messages again every 5 seconds if there are no messages.
My solution
I'have created a Timer in my stateful widget.
Timer timer;
When I build a widget, I check for messages. If there are none, I start the timer. I want the timer to stop at the next check, in case of existing messages
void onBuild() {
if (state.messages.isEmpty) {
_checkEmptyMessages();
timer = Timer.periodic(
Duration(seconds: 5), (Timer t) => _checkEmptyMessages());
}
}
void _checkEmptyMessages() {
print('MES789 ${state.messages.isEmpty}');
if (state.messages.isEmpty) {
add(ChatEventLoadFirstPage()); // This adds an event to the BLoC
} else {
if (timer != null) timer.cancel();
timer = null;
}
}
Also I've tried
I've tried to remove timer = null; and await for timer.cancel();, but it didn't help.
Actual output
So in the Debug Console I get this every 5 seconds:
I/flutter (13387): MES789 false
I/flutter (13387): MES789 false
I/flutter (13387): MES789 false
I/flutter (13387): MES789 false
Question
How can I stop the Timer?
Because 'Timer.periodic' is called, new Timer instance is created and stored same timer variable.
It means that not canceled timer's instance will be lost when 'Timer.periodic' is called.
So you need to check whether Timer instance is exist.
void onBuild() {
if (state.messages.isEmpty) {
if (timer == null) {
timer = Timer.periodic(
Duration(seconds: 5), (Timer t) => add(ChatEventLoadFirstPage()));
}
} else {
if (timer != null) timer.cancel();
timer = null;
}
}
this code is dead code, it will never work because you have if (state.messages.isEmpty) before _checkEmptyMessages() and if (state.messages.isEmpty) again, try to remove first if (state.messages.isEmpty)

setState() or markNeedsBuild() called during build. Trying to make a simple timer in flutter

I am trying to make a simple timer which run till a given time. This is how I have tried to call the timer function. It gives the error as mentioned in the title. I believe the error is there because I am calling set state method in the init state, but I really need to make this functionality that, when this widget enters the screen, a timer begins and do something when the timer ends. Any help is greatly appreciated.
late double timeRemaining;
late Timer _timer;
void startTimer(double timeRemaing) {}
#override
void initState() {
timeRemaining =
widget.startDate.difference(widget.endDate).inSeconds / 1000 - 80;
const Duration seconds = Duration(seconds: 1);
_timer = Timer.periodic(seconds, (timer) {
setState(() {
timeRemaining--;
if (timeRemaining <= 0) {
// done = true;
done = true;
timer.cancel();
}
});
});
super.initState();
}
as the title says, you're building widget during another build (when you call setState in the timer).So the solution is to wait for the widget to finish building, then start your timer, this can be done by using addPostFrameCallback, like the following:
#override
void initState() {
timeRemaining =
widget.startDate.difference(widget.endDate).inSeconds / 1000 - 80;
const Duration seconds = Duration(seconds: 1);
// this will schedule a callback for the end of this frame.
WidgetsBinding.instance.addPostFrameCallback((_) {
_timer = Timer.periodic(seconds, (timer) {
setState(() {
timeRemaining--;
if (timeRemaining <= 0) {
// done = true;
done = true;
timer.cancel();
}
});
});
});
super.initState();
}
try it and tell me if this works

Managing multiple setState() methods in single class?

I am creating a timer when it will come to 0. It will navigate to the next Page.(Not created navigation yet). Till the timer is on user can answer the questions. The problem is I want to start the timer as the StartGame() file opens. In main.dart I have created a navigation for this on click on button. But when this page loads it automatically calls both ( in void getNum() and startTime() ) setState methods() one by one. Therefore answers (options) that I created as buttons automatically changes without onPressed by user. But I only want startTime() method to be called once as widget builds / inits. then after the getNum() method on every click of user.
How do i make these two setState() methods to be called individually without affecting each other.
class StartGame extends StatefulWidget {
StartGameState createState() => StartGameState();
}
class StartGameState extends State<StartGame> {
int no1, no2, no3, no4, inp1, inp2;
int pos;
int res;
List<int> answers;
GenerateQuestion g = new GenerateQuestion();
void getNum() {
answers = g.generateNum();
pos = g.answerPosition();
inp1 = g.generateValue();
inp2 = g.generateValue();
res = inp1 + inp2;
answers.add(res);
answers.shuffle();
setState(() {
no1 = answers[0];
no2 = answers[1];
no3 = answers[2];
no4 = answers[3];
print("set state 1 called");
});
} // void get Num ends here
//counter timer starts from herer
int counter = 10;
Timer timer;
void startTime() {
counter = 10;
timer = new Timer.periodic(Duration(seconds: 1), (timer) {
if (counter > 0) {
setState(() {
counter--;
print("set state 2 called");
});
} else {
timer.cancel();
}
});
}
You can achieve this using the initState() method, it gets only called once when the widget is built.
class _StartGameState extends State<StartGame> {
#override
void initState() {
startTime(); //this function only gets called once
super.initState();
}
//your Code...

Flutter stopwatch background running

I am trying to make a simple stopwatch application and I have so far implemented a stopwatch that is working while the app is running, but I want the stopwatch to continue to work while the application is closed and then resume while I open the app again. A similar app that I can compare to is RunKeeper which tracks the time of your running activities.
Is there any simple plugins that I can use to resolve my issue or is it more complicated than that?
So far I have tried using workmanager(flutter plugin) but with workmanager I could only track background activities at a given time, not the entire time the stopwatch is active, which I want in my case.
I created a stopwatch by using a Stream that returns the time with the help of Timer.periodic(). If you quit to home screen of the phone the Stream keeps running in the background.
Stream<int> stopWatchStream() {
StreamController<int> streamController;
Timer timer;
int counter = 0;
void stopTimer() {
if (timer != null) {
timer.cancel();
timer = null;
counter = 0;
streamController.close();
}
}
void tick(_) {
counter++;
streamController.add(counter);
}
void startTimer() {
timer = Timer.periodic(Duration(milliseconds: 10), tick);
}
streamController = StreamController<int>(
onListen: startTimer,
onCancel: stopTimer,
onResume: startTimer,
onPause: stopTimer,
);
return streamController.stream;
}

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