Fork me on GitHub

TomasMikula/blog

Timers in JavaFX and ReactFX

The takeaway from this post should be that to schedule future actions in a JavaFX application you don’t need an external timer (and thus an extra thread in your application), such as java.util.Timer or java.util.concurrent.ScheduledExecutorService. You can use Timeline or FxTimer, a convenient wrapper around Timeline used internally in ReactFX and recently exposed as public API.

One-time action

This is how to use Timeline to schedule a one-time action:

Timeline timeline = new Timeline(new KeyFrame(
        Duration.millis(2500),
        ae -> doSomething()));
timeline.play();

Equivalently, you can use this ReactFX shortcut:

FxTimer.runLater(
        Duration.ofMillis(2500),
        () -> doSomething());

In addition to the latter being more readable, FxTimer uses the new Java Time API introduced in JDK 8.

Periodic action

This is how to schedule a periodic action using Timeline:

Timeline timeline = new Timeline(new KeyFrame(
        Duration.millis(2500),
        ae -> doSomething()));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();

Using FxTimer, it looks like this:

FxTimer.runPeriodically(
        Duration.ofMillis(2500),
        () -> doSomething());

Or, because we really like event streams:

EventStreams.ticks(Duration.ofMillis(2500))
        .subscribe(tick -> doSomething());

Timer cancellation

Let’s see how one can cancel a scheduled action.

The Timeline way:

Timeline timeline = new Timeline(new KeyFrame(
        Duration.millis(2500),
        ae -> doSomething()));
timeline.play();

// later
timeline.stop();

The FxTimer way:

Timer timer = FxTimer.runLater(
        Duration.ofMillis(2500),
        () -> doSomething());

// later
timer.stop();

There is one important semantic difference between the above code snippets. In the first one, doSomething() may still be executed after timeline.stop(), even though both timeline.stop() and doSomething() are executed on the JavaFX application thread. It happens when stop() is called after the timeline has reached the key frame, but before the associated action had a chance to be executed on the JavaFX application thread. EDIT: Note that this behavior makes sense for animations: if there is an action a associated with time t1 and stop() is called at time t2 > t1, then, if not already done so, a should still be executed in order to advance the animation to a state corresponding to t2.

On the other hand, for timers it makes sense to cancel the timer upon stop() even if the timer is already overdue. Therefore, FxTimer takes an extra measure to ensure that the action is never executed after stop().

Here is the code that shows that behavior.
comments powered by Disqus