TSM - Concurrency and data binding in JavaFX

Silviu Dumitrescu - Line manager@Telenav

This month, too, we bring to your attention some technical challenges of the JavaFX world. In the second article we will discuss about concurrency and data binding.

The javafx.concurrent package manages the multithread code of the interaction with the UI and ensures that this interaction takes place in the correct thread. The package consists of the Worker interface and two basic classes, Task and Service, both of them implementing the Worker interface.

The Worker interface provides the API used by a background worker which communicates with the UI. The Task class is a fully observable implementation of the java.util.concurrent. FutureTask class and it allows developers to implement asynchronous tasks in the JavaFX applications. The Service class performs these tasks.

A Worker is, thus, an object working in a background thread. The state of the Worker object is observable and usable from the threads of the JavaFX application.

The lifecycle of a Worker is defined like this: when the Worker object is created, it is in the READY state. After it has been programmed for work, the Worker object makes the transition towards the SCHEDULED state. Afterwards, when the Worker object is running, its state becomes RUNNING.

Note: Even if the Worker object started immediately, without being programmed, it first passes through the SCHEDULED stage and then goes to RUNNING.

The state of the Worker object, when it has successfully performed becomes SUCCEEDED, and the value property will be set according to the result of the Worker object. Thus, if exceptions are thrown during the runtime of the Worker object, its state becomes FAILED, and the exception property is set to the type of exception that has occurred. The Worker object can be interrupted in any state, by using the cancel () method, which sends the object into the CANCELLED state.

The progress registered on the running of the Worker object may be obtained through three different properties: totalWork, workDone and progress.

The Task class can be started in one of the following ways (the first two would be preferable):

The Tasks are used to implement the work logic in a background thread. Firstly, we have to extend the Task class, which will overwrite the method as call(). The Task class inherits the java.utils.concurrent.FutureTask class, which implements the Runnable interface. That is why the Task object can be used with the Executor API and can be sent to a thread as parameter.

We can call the Task object right from the FutureTask.run (), which allows us to call this task from another thread.

We will create a CounterTask class, which extends the Task class.

public class CounterTask extends Task {
    @Override
    public Void call() {
        final int max = 10000000;
        updateProgress(0, max);
        for (int i = 1; i <= max; i++) {
            updateProgress(i, max);
        }
        return null;
    }
}

The call() method is invoked by the background thread; therefore, this method can manipulate states that are sure to be read or written in a background thread. For instance, manipulation the active graphical stage from the call() method will throw a runtime exception.

On the other hand, the Task class is meant to be used with JavaFX applications and it assures us that any modifications of the public properties, error notifications, event and stage manipulators appear in the JavaFX application thread. Within the call() method, we may use the methods: updateProgress(), updateMessage() and updateTitle() in order to update the values corresponding to the properties on the JavaFX thread.

In the application, we created an instance of the previous class, called countTask and we executed it through an `ExecutorService (ExecutorService es = Executors.newSingleThreadExecutor (); ):`

@Override
public void handle(ActionEvent event) {
    System.out.println("Count Started");
    bar.progressProperty().bind(countTask.progressProperty());
    es.execute(countTask);
}

The Service class is designed to execute a Task object in one or several threads. The methods and states of the Service class must be accessed from the JavaFX application thread. This class helps developers to implement a correct interaction between the background threads and the JavaFX application thread. We can start, stop, cancel and restart a Service. A Service may run a task more than once. Thus, a service may be declared and restarted upon request.

A Service can be run in one of the following ways:

Here we have an illustration of creating a custom service in the example below:

public class CounterService extends Service {
    @Override
    protected Task createTask() {
        CounterTask ct = new CounterTask();
        return ct;
    }
}

Data Binding

Data binging has the purpose of simplifying the task by synchronizing the view with the data of the model. The binding observes its lists of functions in order to detect changes and it updates if they have appeared. The binding API provides a simple way to create bindings for the most common situations.

Binding is, thus, a powerful mechanism for expressing direct relations between variables. When the objects take part in bindings, the changes brought to one of them will automatically reflect upon the other. For instance, binding may be used in GUI for the automatic keeping of the read-outs synchronized with the data they refer.

The bindings are assembled from one or several sources called dependences.

In our previous example, we have used the bind () function to connect the progress bar to the counter Task. Here is the complete code of the JavaFX application:

public class CounterBarAppService extends Application {
    StackPane root = new StackPane();
    VBox mainBox = new VBox();
    ProgressBar bar = new ProgressBar(0.0);
    CounterService cs = new CounterService();

    @Override
    public void init() throws Exception {
        super.init();

        mainBox.setAlignment(Pos.CENTER);
        mainBox.setSpacing(10);

        Button btn = new Button();
        btn.setText("Count to Ten Million!");
        btn.setOnAction(new EventHandler() {

            @Override
            public void handle(ActionEvent event) {
                System.out.println("Count Started");
                bar.progressProperty().bind(cs.progressProperty());
                if (cs.getState() == State.READY) {
                    cs.start();
                }
            }
        });

        Button restartBtn = new Button();
        restartBtn.setText("Restart");
        restartBtn.setOnAction(new EventHandler() {

            @Override
            public void handle(ActionEvent event) {
                System.out.println("Count Started");
                bar.progressProperty().bind(cs.progressProperty());
                cs.restart();
            }
        });

        mainBox.getChildren().add(btn);
        mainBox.getChildren().add(restartBtn);
        mainBox.getChildren().add(bar);
        root.getChildren().add(mainBox);
    }

    @Override
    public void stop() throws Exception {
        super.stop();
    }

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("JavaFX Service Example");

        primaryStage.setScene(new Scene(root, 400, 250));
        primaryStage.show();
    }
}

We are looking forward to discussing the new JavaFX world with you.

Enjoy your reading!