Making a timer in JavaFX

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.

Issues with JavaFX

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.

Make it noisy

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.

Sample Code

Timer example