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

MQTT.fx 0.0.3 released

Download latest binaries HERE.

Most significant change: I have added a pane to show some status values of the broker:

mqtt-0.0.3

In v0.0.3 these $SYS topics are recognised (Tooltips are showing the descriptions):

Version: $SYS/broker/version
Build: $SYS/broker/timestamp
Uptime: $SYS/broker/uptime
Subscriptions Count: $SYS/broker/subscriptions/count
Changeset: $SYS/broker/changeset
Clients Connected: $SYS/broker/clients/active
Clients Expired: $SYS/broker/clients/expired
Clients Disconnected: $SYS/broker/clients/inactive
Clients Maximum: $SYS/broker/clients/maximum
Clients Total: $SYS/broker/clients/total
Connection: $SYS/broker/connection/#
Messages Inflight: $SYS/broker/messages/inflight
Messages Received: $SYS/broker/messages/received
Messages Sent: $SYS/broker/messages/sent
Messages Stored: $SYS/broker/messages/stored
Messages Publish Dropped: $SYS/broker/publish/messages/dropped
Messages Publish Received: $SYS/broker/publish/messages/received
Messages Publish Sent: $SYS/broker/publish/messages/sent
Messages Retained Count: $SYS/broker/retained/messages/count
Bytes Received: $SYS/broker/bytes/received
Bytes Sent: $SYS/broker/bytes/sent
Load Connections: $SYS/broker/load/connections/+
Load Bytes Received: $SYS/broker/load/bytes/received/+
Load Bytes Sent: $SYS/broker/load/bytes/sent/+
Load Messages Received: $SYS/broker/load/messages/received/+
Load Messages Sent: $SYS/broker/load/messages/sent/+
Load Publish Dropped: $SYS/broker/load/publish/dropped/+
Load Publish Received: $SYS/broker/load/publish/received/+
Load Publish Sent: $SYS/broker/load/publish/sent/+
Load Sockets Count: $SYS/broker/load/sockets/+