At your Service!

The Service class (javafx.concurrency.Service) may not only be used to keep the UI reactive on long duration tasks but can also be used to change the state of controls or keep UI in a reasonable state on short term actions.

Recall that a Service is like a reusable Task. A Task can be executed once and has to be re-instantiated to be started again. The Service class creates a new Task each time it is (re-)started (createTask() is called then).

This is the start()-method from javafx.concurrency.Service class:

public void start() {

   checkThread();

    if (getState() != State.READY) {
        throw new IllegalStateException(
                "Can only start a Service in the READY state. Was in state " + getState());
    }

    // Create the task
    task = createTask();

    // Wire up all the properties so they use this task
    state.bind(task.stateProperty());
    value.bind(task.valueProperty());
    exception.bind(task.exceptionProperty());
    workDone.bind(task.workDoneProperty());
    totalWorkToBeDone.bind(task.totalWorkProperty());
    progress.bind(task.progressProperty());
    running.bind(task.runningProperty());
    message.bind(task.messageProperty());
	title.bind(task.titleProperty());

    // Advance the task to the "SCHEDULED" state
    task.setState(State.SCHEDULED);

    // Start the task
    executeTask(task);
}

Note that the properties binding of the Task (reset() is cleaning up all stuff).

In this post want to show how I’m using Services together with a JavaFX UI, like in MQTT.fx to connect or disconnect to a MQTT broker.

A simple connection pane with 5 controls:

connectionPane

Wanted appearance / behavior:
connectButton: only enabled if: not connected AND no service is running
disconnectButton: only enabled if: connected AND no service is running
cancelButton & progressIndicator: only visible if: any service is running
serviceMessageLabel: user feedback about the states of the services and the application

start

connecting

connected

disconnecting

The controller has two properties and contains two services (inner classes):

StringProperty statusMessagesProperty
BooleanProperty connectedProperty

ConnectService

private class ConnectService extends Service<Void> {

        @Override
        protected void succeeded() {
            statusMessagesProperty().set("Connected.");
            connectedProperty().set(true);
        }

        @Override
        protected void failed() {
            statusMessagesProperty().set("Connecting failed.");
            LOGGER.severe(getException().getMessage());
            connectedProperty().set(false);
        }

        @Override
        protected void cancelled() {
            statusMessagesProperty().set("Connecting cancelled.");
            connectedProperty().set(false);
        }

        @Override
        protected Task<Void> createTask() {
            return new Task<Void>() {
                @Override
                protected Void call() throws Exception {
                    updateMessage("Connecting....");
                    Thread.sleep(1000);
                    // DEMO: un-comment to provoke "Not on FX application thread"-Exception:
                    // connectButton.setVisible(false);
                    updateMessage("Waiting for server feedback.");
                    Thread.sleep(2000);
                    return null;
                }
            };
        }

    }

DisconnectService

    private class DisconnectService extends Service<Void> {

        @Override
        protected void succeeded() {
            statusMessagesProperty().set("");
            connectedProperty().set(false);
        }

        @Override
        protected void cancelled() {
            statusMessagesProperty().set("Disconnecting cancelled.");
            connectedProperty().set(false);
        }

        @Override
        protected Task<Void> createTask() {
            return new Task<Void>() {
                @Override
                protected Void call() throws Exception {
                    updateMessage("Disconnecting....");
                    Thread.sleep(1000);
                    updateMessage("Waiting for server feedback.");
                    Thread.sleep(2000);
                    return null;
                }
            };
        }

    }

The wanted UI-behavior can be accomplished just by some Bindings

BooleanBinding anyServiceRunning = connectService.runningProperty().or(disconnectService.runningProperty());
serviceRunningIndicator.visibleProperty().bind(anyServiceRunning);
cancelButton.visibleProperty().bind(anyServiceRunning);
connectButton.disableProperty().bind(connectedProperty().or(anyServiceRunning));
disconnectButton.disableProperty().bind(connectedProperty().not().or(anyServiceRunning));
messagesLabel.textProperty().bind(statusMessagesProperty());

ChangeListeners are passing the messages from the services to the statusMessagesProperty:

connectService.messageProperty().addListener((ObservableValue observableValue, String oldValue, String newValue) -> {
statusMessagesProperty().set(newValue);
});
disconnectService.messageProperty().addListener((ObservableValue observableValue, String oldValue, String newValue) -> {
statusMessagesProperty().set(newValue);
});

Three methods to control the services:

   @FXML
    public void cancel() {
        LOGGER.info("cancel");
        connectService.cancel();
        disconnectService.cancel();
    }

    @FXML
    public void connect() {
        LOGGER.info("connect");
        disconnectService.cancel();
        connectService.restart();
    }

    @FXML
    public void disconnect() {
        LOGGER.info("disconnect");
        connectService.cancel();
        disconnectService.restart();
    }

Nice & convenient:
Instead of verifying the state of the Service you can just call restart().
restart() executes:
1. cancel() (if running): cancel the executed service-task
2. reset(): clear the Service (task=null and unbind all stuff)
and then
3. start(): create new Task and bind its properties

You can get the complete code of this tutorial in my BitBucket repo:

ServiceDemoApp
https://bitbucket.org/Jerady/servicedemoapp/src

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>