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.