Introduction
For one of my requirements for an Android app, I encountered a need for a countdown timer in which timer value could be modified without recreating the timer. For example, if a countdown timer is first created for a duration of 10 minutes, during the course of processing on user’s/app logic demand, I should be able to modify this duration from 10 minutes to say, 14 minutes. Android provides an efficient CountDownTimer
class which is good as a plain vanilla Timer but does not provide this particular feature for modifying the duration. If required, you need to cancel the current timer instance and then create another one with the altered duration.
Background
According to me, this requirement is so simple, easy to code and quite a frequent use case, that it should be provided as a standard class. During my search, I found it is not available anywhere. I thought of creating a CountDownTimer
which provides this feature. I would not like to reinvent the wheel and hence took the CountDownTimer
class provided by Android for the basic structure with some basic differences. The class provided by Android uses Handlers to generate timer events whereas the proposed class uses ScheduledExectuorService
to do so.
Using the Code
The code shown below is developed for Android. But this could be used for a Java application also, with minor tweakings by removing the constructs like Handler which are Android specific.
As expected, countdown timer provides the following behaviours to caller:
- Ability to start the timer for an assigned duration
- Ability to cancel the timer, if required, before the assigned duration
- Should stop by itself when the assigned duration elapses
- Ability to generate events and hence invoke a callback periodically at an interval specified by caller
In addition to these functionalites, this countdown timer also provides for:
- Ability to extend the duration while the timer is still going on
Description of Code
TimerTickListener
- The implementation for this interface is supposed to be provided to the timer during construction. It provides callback methods which are invoked in various situations, i.e., onTick
is invoked when tick
event is generated at a periodic interval, onFinish
is invoked when the duration for timer has elapsed and onCancel
is invoked when the timer is forcefully canceled. TimerRunnable
- This is the core of timer which provides the code which is executed by the scheduler at regular intervals. Here, we have put in the logic for timer to behave in the fashion we want according to the current state of timer. You must have noticed that this code is doing it in the main thread by posting this execution to the main thread handler. This was a requirement for my application to do it in the main thread, you may choose to perform this in the same thread depending on your requirement. - The
run
method of TimerRunnable
identifies the state of timer and acts accordingly.
- If the state is cancelled, it invokes
onCancel
method of the TimerTickListener
callback and shuts down the scheduler. - If the duration has elapsed, it invokes
onFinish
method of the TimerTickListener
callback and shuts down the scheduler. - Else it simply invokes the
onTick
method of the TimerTickListener
callback passing in the time left for the timer to expire.
Constructor
- Apart from setting the various instance variables, taking a callback implementation for TimerTickListener
and saving it with itself, what it does significantly, is creating a SingleThreadScheduledExecutor
assigned to instance variable scheduler. Start
- When this method is invoked, the scheduler created in the constructor is scheduled to run at a periodic time interval passed in constructor and invokes the run
method of an inner runnable class TimerRunnable
. onCancel
- This puts the timer into cancelled state extendTimer
- This extends the duration of timer
with the amount passed in to this method.
public class CustomCountDownTimer {
private Handler mainThreadHandler = new Handler(Looper.getMainLooper());
public interface TimerTickListener{
public void onTick(long millisLeft);
public void onFinish();
public void onCancel();
}
private class TimerRunnable implements Runnable{
public void run(){
mainThreadHandler.post(new Runnable() {
long millisLeft = stopTimeInFuture - SystemClock.elapsedRealtime();
@Override
public void run() {
if (isCancelled){
tickListener.onCancel();
scheduler.shutdown();
}
else if (millisLeft <= 0) {
tickListener.onFinish();
scheduler.shutdown();
}
else{
tickListener.onTick(millisLeft);
}
}
});
}
}
private long millisInFuture;
private final long countdownInterval;
private long stopTimeInFuture;
private boolean isCancelled = false;
private TimerTickListener tickListener;
private ScheduledExecutorService scheduler;
public CustomCountDownTimer(long millisInFuture, long countDownInterval,
TimerTickListener tickListener) {
this.millisInFuture = millisInFuture;
stopTimeInFuture = SystemClock.elapsedRealtime() + this.millisInFuture;
countdownInterval = countDownInterval;
this.tickListener = tickListener;
scheduler = Executors.newSingleThreadScheduledExecutor();
}
public synchronized void start() {
isCancelled = false;
scheduler.scheduleWithFixedDelay(new TimerRunnable(), 0, countdownInterval,
TimeUnit.MILLISECONDS);
}
public synchronized final void cancel() {
isCancelled = true;
}
public void extendTime(long delta){
stopTimeInFuture = stopTimeInFuture + delta;
millisInFuture = millisInFuture + delta;
}
}