In the pomodoro tab in the Time Management Suite application, the
"tomato timer" is the main focus. In order to make a timer that performs
an action when it completes, we need to use the Timer and
TimerTask classes, located in java.util.
java.util.Timer
The Timer class schedules tasks to be executed later.
Creating a new Timer object spawns a new thread that
can execute code after a specified amount of time. The developer can
specify if the timer should run once, or at a repeated interval. To make
a timer that will print "hello world" after 10 seconds, do the following:
Timer myTimer = new Timer();
myTimer.schedule(new TimerTask(){
@Override
public void run() {
System.out.println("hello world");
}
}, 10000);
The above code creates an instance of the Timer class, which
creates a new thread. Then we call the schedule method, and
pass in an anonymous TimerTask class, overriding the
run method. The run method contains the code we
want to execute when the time is up. In our case, we print "hello world".
The next parameter we pass into the schedule method is the
delay in milliseconds. Since we want to print "hello world" after 10
seconds, we pass in 10,000 milliseconds for the delay. Now we
successfully have a non-blocking timer!
We can just as easily create a timer that will perform a task repeatedly
at a fixed interval. Just call scheduleAtFixedRate.
...
Timer myTimer = new Timer();
myTimer.scheduleAtFixedRate(myTimerTask, 0, 10000);
This code creates a timer that executes a task immediately, and executes
the same task every 10000 milliseconds until told to stop with the
cancel method.
After calling cancel on a Timer object, the
timer can't be scheduled again.
The Timer class can have many more uses than creating simple
timers. We can, for example, schedule a task to run at a specific date.
Check out all the cool stuff you can do at the documentation.
java.util.TimerTask
The TimerTask class is responsible for the code that can be
executed by the timer. TimerTask is an abstract class, it
extends Runnable, but does not implement the
run method. We can create a subclass of
TimerTask that overrides the run method, and
put all the code that needs to be run whenever the timer fires inside the
overridden run method. To put it all together, pass an
instance of the subclass to Timer.schedule. Alternatively,
we can pass an anonymous class to Timer.schedule, as seen
in the first example.
Everything we have seen so far isn't concerned with JavaFX. However, when
using timers in a JavaFX GUI, we can run into some issues. Inside the
overridden run method of a subclass of
TimerTask, we aren't able to set certain properties on UI
elements such as a TextField.
For example, if we try to set the promptText on a
TextField object in a timer thread by calling the
TextField.setPromptText method, we will get the
exception:
Exception in thread "Timer-0"
java.lang.IllegalStateException: Not on FX application thread;
currentThread = Timer-0
This means that we need to call TextField.setPromptText on
the JavaFX application thread, instead of the timer thread - "Timer-0".
To execute this method on the JavaFX thread, we can use
Platform.runLater, which takes a Runnable as
it's only parameter. The code in the run of the passed in
Runnable is placed in the JavaFX event queue to be run
later. runLater returns immediately after placing the
passed in Runnable in the event queue, so this call is
non-blocking.
Since Platform.runLater takes Runnable, a
functional interface, as a parameter, we can use a lambda to execute the
TextField.setPromptText method safely.
Platform.runLater(() -> myTextField.setPromptText("Hi!"));
No more exceptions! Yay! When I was working on this program, I only
found this was a problem with the TextField.setPromptText,
but I'm sure there are other methods that need to execute on the JavaFX
main thread.
Now we can make a timer that doesn't throw an exception in our JavaFX
application. But a timer needs to make some noise! We can use the
AudioClip class provided in the JavaFX library. To use it,
we just need to create an instance, passing a sound file to the
constructor, then call play.
AudioClip myClip = new AudioClip(getClass().getClassloader()
.getResource("Bell.mp3").toString());
myClip.play();
In the example, we need to have a file called "Bell.mp3" on the classpath
for the code to run without error. If we want to specify a volume, we can
by passing a double between 0 and 1 to play. Note that since
AudioClip loads the entire sound file into memory, the class
is not well suited for large sound files.